Являются ли лямбды без захвата структурными типами?

P1907R1, допускается для C + +20, введены структурные типы, которые являются допустимыми типами для параметра шаблона, не являющегося типом.

GCC и Clang принимают следующий фрагмент кода для C ++ 2a:

template<auto v>
constexpr auto identity_v = v;

constexpr auto l1 = [](){};
constexpr auto l2 = identity_v<l1>; 

подразумевая, что тип лямбды без захвата является структурным типом.

Вопрос

  • Действительно ли лямбда без захвата отвечает требованиям к тому, чтобы ее тип был структурным?

person dfrib    schedule 21.10.2020    source источник
comment
Как и в некоторых предыдущих случаях, я ответил на мой честный вопрос в процессе подробного его написания.   -  person dfrib    schedule 21.10.2020
comment
По теме: stackoverflow.com/questions/62324050/   -  person Amir Kirsh    schedule 21.10.2020
comment
@AmirKirsh спасибо, я вижу, что я действительно прокомментировал этот конкретный ответ. Ключ, которого мне не хватало (когда я писал этот вопрос), заключался в том, является ли деструктор лямбда-выражения без захвата constexpr или нет, что является требованием для того, чтобы его тип закрытия был буквальным типом (требование, которое не рассматривается в связанных отвечать). Я нашел эту гарантию через (косвенное обращение?), Что A) деструктор по умолчанию является constexpr, если это возможно, и B), который неявно определен деструкторы подпадают под общий термин деструкторов по умолчанию.   -  person dfrib    schedule 21.10.2020


Ответы (1)


Все стандартные ссылки ниже, если явно не указано иное, относятся к N4861 (март 2020 г. послепражский рабочий проект / C ++ 20 DIS).


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

В дальнейшем мы будем называть тип лямбды исключительно типом замыкания.

Как показано в стандартных отрывках ниже, тип закрытия лямбды без захвата:

  • удовлетворяет требованиям, чтобы он был литеральным (классом) типом, и более того
  • выполняет требования к литеральному типу, чтобы он был структурным типом,

и, таким образом, может использоваться как тип для параметра шаблона, не являющегося типом, например, фрагмент примера

template<auto v>
constexpr auto identity_v = v;

constexpr auto l1 = [](){};
constexpr auto l2 = identity_v<l1>;

действительно хорошо сформирован.


Тип закрытия лямбды - это тип класса без объединения

Как регулируется [expr.prim.lambda.closure] / 1 [выделено мое]

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

тип замыкания - это тип класса без объединения.


Тип закрытия лямбда-выражения без захвата - это литерал (класс).

Как регулируется [basic.types] / 10 [извлечение, акцент мой]

Тип является буквальным типом, если он:

  • [...]
  • a possibly cv-qualified class type that has all of the following properties:
    • it has a constexpr destructor ([dcl.constexpr]),
    • это либо тип закрытия ([expr.prim.lambda.closure]), либо агрегированный тип ([dcl.init.aggr]), либо имеет хотя бы один constexpr конструктор или шаблон конструктора (возможно, унаследованный от базового класса), который не является конструктором копирования или перемещения,
    • если это объединение, по крайней мере, один из его нестатических элементов данных имеет энергонезависимый литеральный тип, и
    • если это не объединение, все его нестатические элементы данных и базовые классы имеют энергонезависимые литеральные типы.

тип закрытия является буквальным типом, если

  • имеет деструктор constexpr, и если
  • все его нестатические элементы данных относятся к энергонезависимым литеральным типам.

Тип закрытия лямбда-выражения без захвата не имеет нестатических элементов данных, поэтому последнее требование выполняется. А что насчет первого, деструктора constexpr?

Неявно сгенерированный деструктор constexpr

Как регулируется [expr.prim.lambda.closure] / 14

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

деструктор типа закрытия объявляется неявно. Кроме того, [/dcl.fct.def.default sizes] 5 описывает [отрывок, выделено мое]

Функции с явно заданными по умолчанию и неявно объявленные функции вместе называются функциями по умолчанию, и реализация должна предоставлять для них неявные определения ([class.ctor ], [class.dtor], [class.copy.ctor], [class.copy.assign]), [...]

что собирательный термин дефолтные функции также включают неявно объявленные деструкторы.

Наконец, [class.dtor] / 9

Деструктор по умолчанию является деструктором constexpr, если он удовлетворяет требованиям для деструктора constexpr ([dcl.constexpr]).

описывают, что деструкторы по умолчанию являются деструкторами constexpr, если они соответствуют требованиям [dcl.constexpr], особенно [dcl.constexpr] / 3 и [dcl.constexpr] / 5 [выдержки, акцент мой]

[dcl.constexpr] / 3 Определение функции constexpr должно удовлетворять следующим требованиям:

  • [...]
  • если функция является конструктором или деструктором, ее класс не должен иметь никаких виртуальных базовых классов;
  • [...]

[dcl.constexpr] / 5 Определение деструктора constexpr, function-body которого не является = delete, должно дополнительно удовлетворять следующему требованию:

  • для каждого подобъекта типа класса или его (возможно, многомерного) массива этот тип класса должен иметь деструктор constexpr.

все это выполняется для типа закрытия лямбда-выражения без захвата (без базовых классов и без подобъектов; см. [intro.object] / 2 для последнего).

Таким образом, тип закрытия лямбды без захвата является буквальным типом.


Тип закрытия лямбды без захвата является структурным типом

Согласно [temp.param] / 6 и [temp.param] / 7 [выдержка, выделение мой]

[temp.param] / 6 Параметр шаблона, не являющийся типом, должен иметь один из следующих (возможно, квалифицированных cv) типов:

  • структурный тип (см. ниже),
  • [...]

[temp.param] / 7

Структурный тип может быть одним из следующих:

  • скалярный тип, или
  • ссылочный тип lvalue, или
  • a literal class type with the following properties:
    • all base classes and non-static data members are public and non-mutable and
    • типы всех базовых классов и нестатических элементов данных являются структурными типами или (возможно, многомерными) их массивами.

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


Некоторые примечания об исходном намерении разрешить типу замыкания лямбды быть буквальным типом

N4487 предложил разрешить определенные лямбда-выражения и операции с некоторыми закрывающими объектами, которые должны появляться в константных выражениях, и содержали специальный раздел, посвященный теме закрывающего типа, являющегося буквальным типом:

Замыкающий объект должен быть литеральным типом, если тип каждого из его элементов данных является литеральным типом.

Тип закрытия в C ++ 14 никогда не может быть буквальным типом - даже если все его члены данных являются литеральными типами - потому что в нем отсутствует конструктор constexpr, который не является конструктором копирования или перемещения. Если бы такому типу замыкания было разрешено иметь неявно определенный конструктор по умолчанию, это был бы constexpr, что сделало бы его буквальным типом. Но поскольку типы замыкания по определению должны иметь удаленные конструкторы по умолчанию, реализации запрещено неявно определять их. [...]

P0170R1, содержащий основную формулировку из N4487 был принят и реализован для C ++ 17.

Однако в то время (C ++ 14 и C ++ 17) деструктор не мог быть constexpr, и поэтому, естественно, не существовало требования, чтобы литеральный тип имел деструктор constexpr; [basic.types] /10.5.1 в N4140 (C ++ 14), а также [basic.types] / 10.5.1 в N4659 (C ++ 17) вместо этого требовал, чтобы деструктор был тривиальным:

Тип является буквальным типом, если он:

  • [...]
  • a class type (Clause [class]) that has all of the following properties:
    • it has a trivial destructor,
    • [...]

P1907R1, поддерживается для C ++ 20 расширено требование к объектам параметров шаблона, которые должны иметь постоянное уничтожение; [temp.param] / 8 [курсив < / strong> мой]:

id-expression с именованием не относящегося к типу параметра-шаблона типа класса T обозначает статический объект продолжительности хранения типа const T, известный как объект параметра шаблона < / em>, значение которого совпадает со значением соответствующего аргумента шаблона после того, как он был преобразован в тип параметра-шаблона. Все такие параметры шаблона в программе одного типа с одинаковым значением обозначают один и тот же объект параметра шаблона. [...] Объект параметра шаблона должен постоянно уничтожаться.

и, P0784R7, также принимается для C ++ 20, в частности, содержал введение уничтожения constexpr, включая обновление требования, чтобы тип был буквальным типом; особенно описано в более ранней версии документа, P0784R1 :

Предлагаемые правила для деструкторов constexpr:

  • [...]
  • Литеральный тип требует деструктора constexpr (ранее требовалось более строгое требование тривиального деструктора)
person dfrib    schedule 21.10.2020
comment
Как вы устанавливаете, что лямбда без захвата не имеет нестатических членов данных? (Не то чтобы это должно…) - person Davis Herring; 21.10.2020
comment
@ Дэвид Херринг: Я согласен, что не должно, но это хороший вопрос; [expr.prim.lambda.closure] / 2 - самое близкое, что я обнаружил: Реализация может определять тип закрытия иначе, чем описано ниже, при условии, что это не изменяет наблюдаемое поведение программы, кроме как путем изменения: ..., но не сомневаюсь он кажется слабее, чем я бы надеялся (с учетом намерения принятых предложений, касающихся лямбда-выражений типа constexpr / literal). Есть предположения? - person dfrib; 21.10.2020
comment
Я не думаю, что это указано (даже без этой явной широты реализации). Мы также не знаем доступа к элементам захвата (в противном случае это не имеет значения, поскольку у них нет имен), поэтому даже лямбда-выражения с некоторыми элементами init-capture также могут быть структурными! Предложение, вероятно, необходимо, если этот вариант использования вообще важен. - person Davis Herring; 21.10.2020
comment
@DavisHerring Понятно, спасибо. Хотя это, возможно, пробел в нормативном тексте, цель связанных принятых предложений (например, N4487: ... если инициализация всех его элементов данных (которые соответствуют для каждого захвата)) и других связанных нормативных сегментов (expr.prim.lambda.capture / 12: [...] дополнительные неназванные нестатические элементы данных [...] Если объявлены, такие нестатические данные члены должны иметь буквальный тип.), возможно, подразумевает, что дополнительные нестатические элементы данных не должны быть ... - person dfrib; 21.10.2020
comment
... разрешено влиять на то, является ли тип закрытия буквальным (а в C ++ 20: структурным) типом или нет. - person dfrib; 21.10.2020
comment
Структурный тип - гораздо более сильное требование, чем буквальный тип, поэтому не очень помогает то, что некоторые «дополнительные» члены гарантированно обладают последним свойством. Я не спорю с «наиболее разумной интерпретацией», как вы говорите, но важно знать, что нет никакой гарантии (пока?). - person Davis Herring; 21.10.2020
comment
@DavisHerring Спасибо, завтра я дополню свой ответ этим обсуждением. - person dfrib; 21.10.2020