Когда-то велась долгая дискуссия о том, следует ли считать типы замыканий структурными типами или нет (по сути, определяя, будут ли они подходить как NTTP). Подробный пост об этом, сделанный пользователем dfrib , можно найти здесь.
Первоначальное беспокойство по этому поводу, похоже, выразил Чжихао Юань еще в марте 2022 года. В конечном итоге это стало отчетом о дефектах CWG 2542, который был принят на заседании рабочей группы в июне 2023 года. Предлагаемое решение по этому отчету о дефектах было одобрен, и в нем очень четко указано, что типы затворов не являются конструктивными типами.
Это изменение было отражено во многих версиях рабочего проекта C++, по крайней мере, вплоть до N4971 . Интересующий отрывок указан под [expr.prim.lambda.closure]/3
. Это также то, что люди найдут, просматривая запись cppreference о лямбда-выражениях, которая ссылается на отчет о дефектах CWG 2542 внизу страницы.
Однако в более поздней версии рабочего проекта C++ N4981 отрывок [expr.prim.lambda.closure]/3 теперь гласит:
Тип закрытия не является агрегатным типом (9.4.2); это структурный введите (13.2) тогда и только тогда, когда лямбда не имеет лямбда-захвата.
Кажется, это отменяет утвержденное решение об отчете о дефектах CWG 2542. Следует ли теперь считать этот отчет о дефектах устаревшим? И на каком совещании/решении/документе типы закрытия теперь стали структурными типами?
Любопытно: есть ли у вас причина (или вы знаете причину), почему лямбда без захвата не должна быть структурным типом?
Что ж, если изменение есть в посттокийском проекте, а не в непосредственно предшествующем, возможно, это что-то связано с токийской встречей?
Суть CWG2542 заключалась в том, что списки захвата не являются общедоступными участниками и автоматически лишают тип замыкания агрегатного и, следовательно, структурного характера. Но в обсуждении не упоминался частный случай пустого захвата. В N4981 этот дефект устранен, заявив, что пустой список захвата делает тип замыкания пустым и, следовательно, структурным. Это исключение из общего случая — точно так же, как неявное преобразование пустого замыкания захвата в указатели функций. Одним из мотивирующих примеров может быть unique_ptr
с немедленным встроенным определением удаления.
И на каком совещании/решении/документе типы закрытия теперь стали структурными типами?
Это был CWG2845, предложенное и принятое решение которого заключалось в том, чтобы сделать лямбду без захвата структурным типом.
Это видно из CWG2845:
Сделайте тип закрытия лямбды без захвата структурным типом.
В выпуске 2542 (утвержденном в июне 2023 г.) все типы замыканий не являются структурными типами, т. е. непригодными для использования в качестве параметров шаблона, не являющихся типами. Это приводит к несогласованности обработки преобразования указателя в функцию для типов замыкания без захватов:
Предлагаемая резолюция (одобрена CWG 2024-02-02):
Изменить пункт 3 7.5.5.2 [expr.prim.lambda.closure] следующим образом:
- Тип замыкания не является агрегатным типом (9.4.2 [dcl.init.aggr]) и не ; это структурный тип (13.2 [temp.param]) тогда и только тогда, когда лямбда-выражение не имеет лямбда-захвата. Реализация может определять тип замыкания иначе, чем...
История здесь была довольно странной.
CWG 2542 — это вопрос, отправленный Чжихао Юанем, который задается вопросом о достоверности этого примера:
template <auto V>
void foo() {}
void bar() {
foo<[i = 3] { return i; }>();
}
Поскольку правила для структурных типов требуют, чтобы все открытые члены, а члены данных лямбды не были названы и не указаны, поэтому неясно, являются ли они общедоступными или нет.
В Core решили решить эту проблему, приложив все усилия и исключив из рассмотрения все лямбды как структурные — независимо от того, есть ли у них захват или нет.
Итак, я представил CWG 2845, опираясь на пример Чжихао, указав, что:
template <auto V>
void foo() {}
void bar() {
foo<[i = 3] { return i; }>(); // #1: error
foo<[]{}>(); // #2: error
foo<+[]{}>(); // #3: OK, a function pointer is a structural type
}
Затем было решено считать лямбды без захвата структурными.
Этот последний вопрос был принят на встрече в Токио в марте 2024 года (отсюда и взята текущая формулировка, цитируемая в ФП), что можно рассматривать как отмену/исправление неправильного решения первого вопроса.
Я понимаю, почему стандарт не хочет быть слишком конкретным, когда речь идет о деталях реализации типов замыканий. И, следовательно, определение захватывающих лямбда-выражений не как структурных типов. Однако я не совсем понимаю, почему приведенный вами пример является таким убедительным контраргументом. Поскольку указатели на функции уже соответствуют требованиям структурных типов, а лямбда-выражения без захвата уже предоставляют простой способ их преобразования в таковые. Зачем заморачиваться с изменением правил, чтобы конкретно определить лямбды без захвата как структурные типы? --
-- Довольно тонкое, но существенное различие между #2
и #3
состоит в том, что каждый вызов #2
приводит к созданию нового экземпляра шаблона foo
. Но какую именно выгоду это дает?
@303 Зачем беспокоиться о разрешении использования лямбда-выражений в качестве параметров шаблона, не относящихся к типу? Поскольку это очевидно полезно (и требовать преобразования в указатель на функцию для достижения этой цели глупо и бессмысленно ограничивает), общие лямбда-выражения не могут быть преобразованы в указатель на функцию, как этот.
@303 Причина Я изначально поднял эту проблему в 2020 году заключается в том, что если лямбда-выражения разрешено использовать в качестве параметра шаблона, не являющегося типом, то это открывает еще одну банку червя для метапрограммирования с сохранением состояния, что еще предстоит официально решенная тема возникла еще в 2015 году через CWG 2118. Хотя внедрение скрытого друга является одновременно хрупким и очень нишевым методом, злоупотребление лямбами в качестве нетиповых параметров шаблона для метапрограммирования с сохранением состояния не так уж и надуманно.
... Меня немного беспокоит возможность непреднамеренного злоупотребления метапрограммированием с сохранением состояния, которое становится все более широко распространенным из-за этого нового «инструмента» (Закон Хайрама , в некотором смысле), кое-что Я подчеркнул в последующих вопросах и ответах.
@dfrib Думаю, в целом меня меньше беспокоит «потенциал непреднамеренного злоупотребления метапрограммированием с сохранением состояния», чем «потенциал целевого использования простой передачи функций обычными способами»
@dfrib "... если лямбда-выражения разрешено использовать в качестве параметра шаблона, не являющегося типом, то это открывает еще одну банку червя для метапрограммирования с сохранением состояния..." Например, безопасная проверка полноты типа, для пример? Однако не разрешение лямбда-выражений в качестве NTTP открывает эту банку с червями, а скорее разрешение лямбда-выражений в неоцененных контекстах. Потому что даже если типы замыканий определены как не структурные типы, все равно остается возможность просто сделать: template<typename = decltype([]{})>
Я считаю это не возвратом, а пересмотром. Они отметили, что в случае отсутствия захвата это является структурным, что очень разумно, и заявили, что в противном случае это не так.