Мы рассматриваем цель создания двух разных типов, используя один и тот же синтаксис. Это легко сделать с помощью лямбд:
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 нет, меня очень удивляет.
ok3true, но ok4false?Примечание: я очень надеюсь, что это функция, а не ошибка, а просто потому, что она делает некоторые сумасшедшие идеи реализуемыми.
Насколько я знаю, так и задумано. Аргументы по умолчанию применяются в контексте вызывающего; поэтому каждый сайт вызова имеет другую лямбду и, следовательно, другой тип. ctad не имеет аргумента по умолчанию. Единственное определение Ланды такое же. Мне было бы любопытно, одинаково ли это, когда аргументы шаблона различаются.
ok2/ok3, для CTAD есть одно определение конструктора, поэтому один экземпляр decltype([]{}).
Хм, понятно... По ссылке @Holt у нас фактически есть defaulted<decltype([]{})>() дважды. И так же, как мы получаем два разных типа для двух []{} с x0 и y0, мы получаем два из них для x2/y2...
хм... почти похоже на еще один способ сделать программирование с сохранением состояния во время компиляции
@GuillaumeRacicot Вы читаете мои мысли...
Сначала я хотел спросить, как это уйдет в stateful constexpr, потом понял, а теперь хотел бы этого не делать, чтобы не думать о неизбежных ужасах, которые обязательно последуют. Отсюда я чувствую агонию обычных писателей...
Я считаю, что вы нарушаете угорь.is/С++ черновик/temp.res#8.5, но я не уверен.
@ Rakete1111 Rakete1111 Не могли бы вы уточнить, какая часть, по вашему мнению, здесь нарушена и почему? По крайней мере, насколько я понимаю, аргументы шаблона по умолчанию не являются частью специализации/экземпляра шаблона, поэтому этот абзац, похоже, здесь не применим!?
@MichaelKenzel они являются частью специализации, если они используются (например, если вы не указываете какие-либо аргументы шаблона)?
@ Rakete1111 Извините, плохой выбор слов с моей стороны. Тип, указанный аргументом по умолчанию, конечно же, будет частью имени конкретной специализации, использующей этот аргумент по умолчанию. Но экземпляр шаблона — это то, что генерируется из шаблона для заданного списка аргументов. У экземпляра нет параметров или аргументов по умолчанию. Все параметры должны быть исправлены еще до создания экземпляра. Так что, по крайней мере, при чтении этого абзаца я не понимаю, как аргументы по умолчанию могут быть одной из «конструкций», которые можно «интерпретировать» по-разному!?
@ Rakete1111: в случае, если ok3 будет неправильно сформирован, будет ли это также в случае ok2?
@MichaelKenzel, о, я понимаю, что ты имеешь в виду. Да, у экземпляра нет аргументов по умолчанию, и да, каждый аргумент необходимо исправить. Но вы можете создать экземпляр шаблона без списка аргументов. Отсутствующие аргументы шаблона затем берутся из аргументов по умолчанию из шаблонов. Таким образом, они могут косвенно участвовать в текущей реализации, если это имеет смысл.
@ Винсент да :)
Моя мысленная модель заключается в том, что аргументы шаблона по умолчанию создаются в момент создания экземпляра, так что foo<> эквивалентно foo<decltype([]{})>. Я признаю, что мне трудно объяснить, почему CTAD ведет себя по-другому. wandbox.org/permlink/LMD2QRA6DPVX3ka3
@LouisDionne Разве это не просто то, что decltype([]{}) не является зависимым именем и, таким образом, уже оценивается в определении руководства по дедукции?
@LouisDionne Хорошо, это не совсем так, но ситуация с CTAD здесь почти последняя: wandbox.org/permlink/TDRgtlNMZmVlUsTa





для функции ok2 тип параметра (T) зависит от указанного параметра шаблона. для ок3 ctor не является шаблоном.
для ok4 оба вывода зависят от одного и того же списка типов параметров (который в данном случае пуст) и из-за этого вывод происходит только один раз. создание экземпляра шаблона и дедукция - разные вещи. в то время как вывод списка одного и того же типа параметра происходит только один раз, создание экземпляра происходит для всех случаев использования.
посмотрите этот код (https://godbolt.org/z/ph1Wk2). если параметры для вычета различны, происходят отдельные вычеты.
Поскольку вопрос помечен language-lawyer, не могли бы вы дать несколько ссылок на стандарт. Может цитата на "вычет происходит только один раз"?
Я не смотрел на стандарт, но мы знаем, что создание экземпляра шаблона может происходить вне определения шаблона. Но дедукция должна быть снабжена соответствующей функцией, и нет никакой необходимости повторять ее более одного раза. Я думаю, что это не указано в стандарте. Также в этом примере Args не зависит от T. Для данной функции "ctad(const Args&...)" становится "ctad()", и из-за этого Args является пустым пакетом параметров.
Could someone provide an explanation of the rules that make ok3 true but ok4 false?
ok3 является истинным, потому что использует тип лямбда-выражения в качестве типа по умолчанию.
The type of a lambda-expression (which is also the type of the closure object) is a unique, unnamed non-union class type,
Следовательно, тип шаблона по умолчанию для object, тип параметра шаблона для macro и singltone всегда отличаются после каждой установки. Но для функции function call возвращаемая лямбда уникальна, и ее тип уникален. Функция шаблона ctad имеет шаблон только для параметров, но возвращаемое значение уникально. Если переписать функцию как:
template <class... Args, class T = decltype([]{})>
ctad(const Args&...) -> ctad<T>;
В этом случае возвращаемый тип будет отличаться после каждого экземпляра.