Имея этот код, 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
}
живой пример
@RaymondChen Ну, это то же самое? Поведение Clang внутренне несогласовано, поскольку он считает, что f()
оценивается как f<decltype([]{})>()
(т. е. каждый вызов оценивается с уникальной лямбдой), а test2.f()
оценивается как test2.f<L3>()
(т. е. каждый вызов оценивается с одной и той же лямбда-выражением). Два других компилятора оценивают аргумент по умолчанию на сайте вызова в каждом случае (последовательно).
f()
и f<decltype([]{})>()
— это не одно и то же. Первый использует лямбду, объявленную в строке 3. Второй использует лямбду, объявленную в строке. Чтобы понять это, нужно заменить все лямбда-выражения явно именованными типами. Создайте новый тип с явным именем для каждого появления лямбды в исходном коде.
@RaymondChen Вопрос указывает на то, что ни gcc, ни clang, ни msvc не оценивают f()
так, как вы описываете.
@Барри Верно, и я понимаю, что clang соответствует стандарту, а другие нет.
@RaymondChen Я думаю, что Кланг ошибается, несмотря ни на что. Либо неверно отвергать последнее утверждение, либо неверно принимать первые два. Вопрос лишь в том, какая сторона права.
Ой, я неправильно прочитал третий тест. Я думал, что третий тест был std::is_same_v<decltype(test1.f()), decltype(test2.f())>)
. Насколько я понимаю стандарт, все три утверждения должны потерпеть неудачу (поскольку первое утверждает, что L1!=L1, второе утверждает, что L2!=L2, а третье утверждает, что L3!=L3. Но кажется, что все три основные составители со мной не согласны, так что я, должно быть, что-то неправильно понимаю.)
К сожалению, это всего лишь пробел в спецификации. Стандарт не объясняет, как должен вести себя любой из этих трех примеров. [expr.prim.lambda.closure]/1 говорит:
Тип лямбда-выражения (который также является типом объекта замыкания) представляет собой уникальный безымянный тип класса без объединения, называемый типом замыкания, свойства которого описаны ниже.
Значение слова «уникальный» никогда не уточнялось. Спецификация аргументов шаблона по умолчанию также не объясняет здесь ничего полезного.
Поскольку аргумент шаблона по умолчанию может ссылаться на более ранние аргументы шаблона в том же списке параметров шаблона, может показаться, что аргумент шаблона по умолчанию может быть создан несколько раз для одного и того же шаблона (по крайней мере, когда в предыдущие аргументы шаблона передаются разные значения). ). Отсюда нетрудно заключить, что, возможно, аргумент шаблона по умолчанию создается заново каждый раз, когда он используется, и если он содержит лямбду, то вы каждый раз получаете другую лямбду (а если он не содержит лямбду тогда вы просто получите то, что получите). Но опять же, в стандарте это прямо не указано.
Я не знаю, о каком «кешировании» вы говорите. Каждая из лямбда-выражений
[](){}
уникальна согласно [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()
.