Разрешено ли компилятору «кэшировать» тип аргумента шаблона по умолчанию?

Имея этот код, GCC и MSVC довольны им, когда clang жалуется. Любые две лямбды должны иметь разные типы, что заставляет меня думать, что clang «кэширует» тип аргумента шаблона по умолчанию (но только если сам класс является шаблоном).

Есть ли какое-то формальное правило для этого? Или это неопределенное/неопределенное/специфическое поведение реализации?

#include <type_traits>

template <typename T = decltype([](){})>
T f();

struct Test1 {
    template <typename T = decltype([](){})>
    T f();
};

template <typename = void>
struct Test2 {
    template <typename T = decltype([](){})>
    T f();
};

int main()
{
    static_assert(!std::is_same_v<decltype(f()), decltype(f())>);

    Test1 test1;
    static_assert(!std::is_same_v<decltype(test1.f()), decltype(test1.f())>);

    Test2 test2;
    static_assert(!std::is_same_v<decltype(test2.f()), decltype(test2.f())>); // GCC and MSVC ok with this line, clang complains
}

живой пример

Я не знаю, о каком «кешировании» вы говорите. Каждая из лямбда-выражений [](){} уникальна согласно [expr.prim.lambda.closure]: «Тип лямбда-выражения — это уникальный безымянный тип класса, не являющийся объединением». Каждое появление [](){} представляет собой отдельный тип, и clang кажется правильным. Это как если бы вы написали struct L1 { void operator()(){}}; struct L2 { void operator()(){}}; struct L3 { void operator()(){}};, а затем использовали template<typename T = L1> вместо f(), template<typename T = L2> вместо Test1::f() и template<typename T = L3> вместо Test2::f().

Raymond Chen 06.09.2024 22:42

@RaymondChen Ну, это то же самое? Поведение Clang внутренне несогласовано, поскольку он считает, что f() оценивается как f<decltype([]{})>() (т. е. каждый вызов оценивается с уникальной лямбдой), а test2.f() оценивается как test2.f<L3>() (т. е. каждый вызов оценивается с одной и той же лямбда-выражением). Два других компилятора оценивают аргумент по умолчанию на сайте вызова в каждом случае (последовательно).

Barry 06.09.2024 22:53
f() и f<decltype([]{})>() — это не одно и то же. Первый использует лямбду, объявленную в строке 3. Второй использует лямбду, объявленную в строке. Чтобы понять это, нужно заменить все лямбда-выражения явно именованными типами. Создайте новый тип с явным именем для каждого появления лямбды в исходном коде.
Raymond Chen 06.09.2024 22:55

@RaymondChen Вопрос указывает на то, что ни gcc, ни clang, ни msvc не оценивают f() так, как вы описываете.

Barry 06.09.2024 23:00

@Барри Верно, и я понимаю, что clang соответствует стандарту, а другие нет.

Raymond Chen 06.09.2024 23:08

@RaymondChen Я думаю, что Кланг ошибается, несмотря ни на что. Либо неверно отвергать последнее утверждение, либо неверно принимать первые два. Вопрос лишь в том, какая сторона права.

Barry 06.09.2024 23:18

Ой, я неправильно прочитал третий тест. Я думал, что третий тест был std::is_same_v<decltype(test1.f()), decltype(test2.f())>). Насколько я понимаю стандарт, все три утверждения должны потерпеть неудачу (поскольку первое утверждает, что L1!=L1, второе утверждает, что L2!=L2, а третье утверждает, что L3!=L3. Но кажется, что все три основные составители со мной не согласны, так что я, должно быть, что-то неправильно понимаю.)

Raymond Chen 07.09.2024 00:47
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
5
7
93
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

К сожалению, это всего лишь пробел в спецификации. Стандарт не объясняет, как должен вести себя любой из этих трех примеров. [expr.prim.lambda.closure]/1 говорит:

Тип лямбда-выражения (который также является типом объекта замыкания) представляет собой уникальный безымянный тип класса без объединения, называемый типом замыкания, свойства которого описаны ниже.

Значение слова «уникальный» никогда не уточнялось. Спецификация аргументов шаблона по умолчанию также не объясняет здесь ничего полезного.

Поскольку аргумент шаблона по умолчанию может ссылаться на более ранние аргументы шаблона в том же списке параметров шаблона, может показаться, что аргумент шаблона по умолчанию может быть создан несколько раз для одного и того же шаблона (по крайней мере, когда в предыдущие аргументы шаблона передаются разные значения). ). Отсюда нетрудно заключить, что, возможно, аргумент шаблона по умолчанию создается заново каждый раз, когда он используется, и если он содержит лямбду, то вы каждый раз получаете другую лямбду (а если он не содержит лямбду тогда вы просто получите то, что получите). Но опять же, в стандарте это прямо не указано.

Другие вопросы по теме