Параметр шаблона по умолчанию и лямбда в недооцененном контексте: ошибка или функция?
Мы рассматриваем цель создания двух разных типов, используя один и тот же синтаксис. Это легко сделать с помощью лямбды:
auto x = []{};
auto y = []{};
static_assert(!std::is_same_v<decltype(x), decltype(y)>);
Но вместо того, чтобы использовать лямбды, мы ищем другой, более элегантный синтаксис. Вот несколько тестов. Начнем с определения некоторых инструментов:
#include <iostream>
#include <type_traits>
#define macro object<decltype([]{})>
#define singleton object<decltype([]{})>
constexpr auto function() noexcept
{
return []{};
}
template <class T = decltype([]{})>
constexpr auto defaulted(T arg = {}) noexcept
{
return arg;
}
template <class T = decltype([]{})>
struct object
{
constexpr object() noexcept {}
};
template <class T>
struct ctad
{
template <class... Args>
constexpr ctad(const Args&...) noexcept {}
};
template <class... Args>
ctad(const Args&...) -> ctad<decltype([]{})>;
и следующие переменные:
// Lambdas
constexpr auto x0 = []{};
constexpr auto y0 = []{};
constexpr bool ok0 = !std::is_same_v<decltype(x0), decltype(y0)>;
// Function
constexpr auto x1 = function();
constexpr auto y1 = function();
constexpr bool ok1 = !std::is_same_v<decltype(x1), decltype(y1)>;
// Defaulted
constexpr auto x2 = defaulted();
constexpr auto y2 = defaulted();
constexpr bool ok2 = !std::is_same_v<decltype(x2), decltype(y2)>;
// Object
constexpr auto x3 = object();
constexpr auto y3 = object();
constexpr bool ok3 = !std::is_same_v<decltype(x3), decltype(y3)>;
// Ctad
constexpr auto x4 = ctad();
constexpr auto y4 = ctad();
constexpr bool ok4 = !std::is_same_v<decltype(x4), decltype(y4)>;
// Macro
constexpr auto x5 = macro();
constexpr auto y5 = macro();
constexpr bool ok5 = !std::is_same_v<decltype(x5), decltype(y5)>;
// Singleton
constexpr singleton x6;
constexpr singleton y6;
constexpr bool ok6 = !std::is_same_v<decltype(x6), decltype(y6)>;
и следующий тест:
int main(int argc, char* argv[])
{
// Assertions
static_assert(ok0); // lambdas
//static_assert(ok1); // function
static_assert(ok2); // defaulted function
static_assert(ok3); // defaulted class
//static_assert(ok4); // CTAD
static_assert(ok5); // macro
static_assert(ok6); // singleton (macro also)
// Display
std::cout << ok1 << std::endl;
std::cout << ok2 << std::endl;
std::cout << ok3 << std::endl;
std::cout << ok4 << std::endl;
std::cout << ok5 << std::endl;
std::cout << ok6 << std::endl;
// Return
return 0;
}
это скомпилировано с текущей транковой версией GCC с опциями -std=C++2a
. Смотрите результат здесь в проводнике компилятора.
Тот факт, что ok0
, ok5
и ok6
работают, не является сюрпризом. Однако тот факт, что ok2
и ok3
true
а ok4
не является для меня очень удивительным.
- Может ли кто-нибудь объяснить правила, которые делают
ok3
true
а ok4
false
? - Это действительно так, как это должно работать, или это ошибка компилятора, связанная с экспериментальной функцией (лямбда-выражения в неоцененном контексте)? (ссылка на стандарт или на C++ предложения приветствуется)
Примечание: я действительно надеюсь, что это особенность, а не ошибка, а только потому, что она делает некоторые безумные идеи реализуемыми
Ответы
Ответ 1
для ok2 тип параметра функции (T) зависит от указанного параметра шаблона. для ok3 ctor это не шаблон.
для ok4 оба вычета зависят от одного и того же списка типов параметров (который в данном случае пуст), и поэтому вычет происходит только один раз. Создание шаблона и дедукция - разные вещи. в то время как для одного и того же типа параметра вывод списка происходит только один раз, создание экземпляра происходит для всех типов использования.
посмотрите этот код (https://godbolt.org/z/ph1Wk2). если параметры для вычета различны, происходят отдельные вычеты.
Ответ 2
Если мы скомпилируем с Clang:
error: lambda expression in an unevaluated operand
template <class T = decltype([]{})>
Подробнее читайте здесь: Почему лямбда-выражения не допускаются в неоцененных операндах, но допускаются в неоцененных частях константных выражений?
Так что, даже если я не являюсь языковым слоем, я бы сказал: это не (верно) C++
Ответ 3
Может ли кто-нибудь объяснить правила, которые делают ok3 истинным, а ok4 ложным?
ok3 верно, потому что в качестве типа по умолчанию используется лямбда-тип.
Тип лямбда-выражения (который также является типом объекта замыкания) является уникальным безымянным типом класса, не являющимся объединением,
Следовательно, тип шаблона по умолчанию для object
, тип параметра шаблона для macro
и singltone
всегда различаются после каждой установки. Но для вызова функции function
возвращает лямбда уникальна и ее тип уникален. Функция шаблона ctad
имеет шаблон только для параметров, но возвращаемое значение является уникальным. Если переписать функцию как:
template <class... Args, class T = decltype([]{})>
ctad(const Args&...) -> ctad<T>;
В этом случае тип возвращаемого значения будет отличаться после каждого экземпляра.