Параметр шаблона по умолчанию и лямбда в неопределенном контексте: ошибка или функция?

Мы рассматриваем цель создания двух разных типов, используя один и тот же синтаксис. Это легко сделать с помощью лямбд:

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?
  • Это действительно так, или это ошибка компилятора, связанная с экспериментальной функцией (лямбда-выражения в невычисленных контекстах)? (ссылка на стандарт или предложения C++ очень приветствуется)

Примечание: я очень надеюсь, что это функция, а не ошибка, а просто потому, что она делает некоторые сумасшедшие идеи реализуемыми.

Бросает плюс и убегает
Quentin 02.04.2019 15:01

Насколько я знаю, так и задумано. Аргументы по умолчанию применяются в контексте вызывающего; поэтому каждый сайт вызова имеет другую лямбду и, следовательно, другой тип. ctad не имеет аргумента по умолчанию. Единственное определение Ланды такое же. Мне было бы любопытно, одинаково ли это, когда аргументы шаблона различаются.

eerorika 02.04.2019 15:10
угорь.is/С++ черновик/temp.arg#8 почти дает вам ответ для ok2/ok3, для CTAD есть одно определение конструктора, поэтому один экземпляр decltype([]{}).
Holt 02.04.2019 15:12

Хм, понятно... По ссылке @Holt у нас фактически есть defaulted<decltype([]{})>() дважды. И так же, как мы получаем два разных типа для двух []{} с x0 и y0, мы получаем два из них для x2/y2...

Aconcagua 02.04.2019 15:25

хм... почти похоже на еще один способ сделать программирование с сохранением состояния во время компиляции

Guillaume Racicot 02.04.2019 15:26

@GuillaumeRacicot Вы читаете мои мысли...

Vincent 02.04.2019 15:34

Сначала я хотел спросить, как это уйдет в stateful constexpr, потом понял, а теперь хотел бы этого не делать, чтобы не думать о неизбежных ужасах, которые обязательно последуют. Отсюда я чувствую агонию обычных писателей...

Max Langhof 02.04.2019 15:54

Я считаю, что вы нарушаете угорь.is/С++ черновик/temp.res#8.5, но я не уверен.

Rakete1111 02.04.2019 16:15

@ Rakete1111 Rakete1111 Не могли бы вы уточнить, какая часть, по вашему мнению, здесь нарушена и почему? По крайней мере, насколько я понимаю, аргументы шаблона по умолчанию не являются частью специализации/экземпляра шаблона, поэтому этот абзац, похоже, здесь не применим!?

Michael Kenzel 02.04.2019 18:14

@MichaelKenzel они являются частью специализации, если они используются (например, если вы не указываете какие-либо аргументы шаблона)?

Rakete1111 02.04.2019 18:18
stackoverflow.com/q/54410522/9585016
Language Lawyer 02.04.2019 18:40

@ Rakete1111 Извините, плохой выбор слов с моей стороны. Тип, указанный аргументом по умолчанию, конечно же, будет частью имени конкретной специализации, использующей этот аргумент по умолчанию. Но экземпляр шаблона — это то, что генерируется из шаблона для заданного списка аргументов. У экземпляра нет параметров или аргументов по умолчанию. Все параметры должны быть исправлены еще до создания экземпляра. Так что, по крайней мере, при чтении этого абзаца я не понимаю, как аргументы по умолчанию могут быть одной из «конструкций», которые можно «интерпретировать» по-разному!?

Michael Kenzel 02.04.2019 19:00

@ Rakete1111: в случае, если ok3 будет неправильно сформирован, будет ли это также в случае ok2?

Vincent 02.04.2019 19:44

@MichaelKenzel, о, я понимаю, что ты имеешь в виду. Да, у экземпляра нет аргументов по умолчанию, и да, каждый аргумент необходимо исправить. Но вы можете создать экземпляр шаблона без списка аргументов. Отсутствующие аргументы шаблона затем берутся из аргументов по умолчанию из шаблонов. Таким образом, они могут косвенно участвовать в текущей реализации, если это имеет смысл.

Rakete1111 02.04.2019 20:48

@ Винсент да :)

Rakete1111 02.04.2019 20:49

Моя мысленная модель заключается в том, что аргументы шаблона по умолчанию создаются в момент создания экземпляра, так что foo<> эквивалентно foo<decltype([]{})>. Я признаю, что мне трудно объяснить, почему CTAD ведет себя по-другому. wandbox.org/permlink/LMD2QRA6DPVX3ka3

Louis Dionne 03.04.2019 18:28

@LouisDionne Разве это не просто то, что decltype([]{}) не является зависимым именем и, таким образом, уже оценивается в определении руководства по дедукции?

Max Langhof 03.04.2019 18:33

@LouisDionne Хорошо, это не совсем так, но ситуация с CTAD здесь почти последняя: wandbox.org/permlink/TDRgtlNMZmVlUsTa

Max Langhof 03.04.2019 18:40
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
39
19
889
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

для функции ok2 тип параметра (T) зависит от указанного параметра шаблона. для ок3 ctor не является шаблоном.

для ok4 оба вывода зависят от одного и того же списка типов параметров (который в данном случае пуст) и из-за этого вывод происходит только один раз. создание экземпляра шаблона и дедукция - разные вещи. в то время как вывод списка одного и того же типа параметра происходит только один раз, создание экземпляра происходит для всех случаев использования.

посмотрите этот код (https://godbolt.org/z/ph1Wk2). если параметры для вычета различны, происходят отдельные вычеты.

Поскольку вопрос помечен language-lawyer, не могли бы вы дать несколько ссылок на стандарт. Может цитата на "вычет происходит только один раз"?

HolyBlackCat 18.04.2019 01:55

Я не смотрел на стандарт, но мы знаем, что создание экземпляра шаблона может происходить вне определения шаблона. Но дедукция должна быть снабжена соответствующей функцией, и нет никакой необходимости повторять ее более одного раза. Я думаю, что это не указано в стандарте. Также в этом примере Args не зависит от T. Для данной функции "ctad(const Args&...)" становится "ctad()", и из-за этого Args является пустым пакетом параметров.

mfurkanuslu 29.04.2019 22:02
Ответ принят как подходящий

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>;

В этом случае возвращаемый тип будет отличаться после каждого экземпляра.

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