Код, как показано ниже или на godbolt, компилируется с помощью gcc и MSVC, но не работает с clang. Я не мог найти, если/где это запрещено в стандарте. На мой взгляд, его нужно поддерживать.
Так кто прав в этом, clang или gcc/MSVC?
#include <type_traits>
void foo() {
static_assert(decltype([_=std::declval<int>()]() consteval noexcept { // clang error: declval() must not be used
if constexpr (std::is_integral<decltype(_)>::value) {
return std::bool_constant<true>();
} else {
return std::bool_constant<false>();
}
}())::value);
}
Пример можно расширить на 3 случая, как показано ниже или на godbolt:
Таким образом, кажется очевидным, что это недопустимо в лямбда-теле, но разрешено снаружи в качестве аргумента вызывающей стороны. Неясно, разрешено ли это в списке захвата.
#include <type_traits>
auto foo_lambda_argument() {
return decltype([](auto _) noexcept {
return std::bool_constant<std::is_integral<decltype(_)>::value>();
}(std::declval<int>()))::value; // OK with clang/gcc/MSVC
}
auto foo_capture_list() {
return decltype([_=std::declval<int>()]() noexcept { // OK with gcc/MSVC; clang error: declval() must not be used
return std::bool_constant<std::is_integral<decltype(_)>::value>();
}())::value;
}
auto foo_lambda_body() {
return decltype([]() noexcept {
auto _=std::declval<int>(); // clang/gcc/MSVC error
return std::bool_constant<std::is_integral<decltype(_)>::value>();
}())::value;
}
Похоже, это потому, что std::declval() не является constexpr функцией. См.: en.cppreference.com/w/cpp/utility/declval
Я не думаю, что static_assert это unevaluated context. Выражение в static_assert оценивается во время компиляции. std::declval() нельзя использовать в проверяемом контексте — это ошибка.
Удали static_assert и та же ошибка с clang: godbolt.org/z/MGzhMd19M
Если вы удалите static_assert, вы получите лямбда. Лямбда — это оцененный контекст, не так ли?
Все дело в использовании std::declval() в лямбда-выражении в неоцениваемых контекстах, а std::declval() может использоваться только в неоцениваемых контекстах. На самом деле не имеет значения, является ли std::decval() constexpr или нет: godbolt.org/z/q6hfKEexf
Кажется сомнительным, чтобы код сохранял результат declval даже в невычисленном контексте. Конкретная ошибка, с которой вы сталкиваетесь, находится в коде библиотеки, чтобы люди не пытались вызвать функцию. GCC выдает ту же ошибку, если я пытаюсь поместить if (std::declval<int>() == 0) в невычисленную лямбду.
IMO if (std::decval<int>()==0), очевидно, является серьезной ошибкой, а пример в посте - нет. Я просто хочу использовать самый простой пример, чтобы показать, в чем может быть проблема, даже если это немного глупо. Но я думаю, что это все еще допустимый вариант использования. Причина, по которой я придумал эту странную вещь, заключается в том, что у MSVC есть какая-то другая ошибка компилятора, если _ является параметром лямбда, и мы разрабатываем кроссплатформенное программное обеспечение.
Случай if использует результат вызова функции точно так же, как ваш код использует его для инициализации захвата. Я не понимаю, чем они отличаются концептуально. Однако, если это вас не устраивает, auto x = std::declval<int>(); это то же самое, и это почти то же самое, что ваш захват делает за кулисами. В любом случае, это соответствует стандарту, чтобы справиться со всем этим.
Я не совсем уверен, как это должно работать, но вот моя попытка решения:
Согласно [intro.execution]/3.3 инициализатор init-capture является непосредственным подвыражением лямбда-выражения. Однако ни один из перечисленных элементов не составляет выражения в подвыражениях тела лямбды.
Невычисленные операнды — это потенциально неоцениваемые выражения. Но только их подвыражения также являются потенциально вычисляемыми выражениями. (см. [basic.def.odr]/2)
Функция используется odr, если она названа потенциально вычисляемым выражением или в некоторых особых случаях, которые здесь не имеют значения.
Так что std::declval должно быть хорошо в инициализаторе init-capture, как в вашем пункте 2.
Это также нормально в пункте 1, потому что аргументы функции являются подвыражениями выражения вызова функции, составляющими весь неоцененный операнд.
Пункт 3 не подходит, потому что выражения в теле лямбда-выражения потенциально оцениваются, даже если лямбда-выражение появляется в невычисленном операнде.
Одно из опасений, связанных с приведенными выше рассуждениями, заключается в том, что [exp.prim.lambda.capture]/6 указывает, что init-capture ведет себя так, как будто объявляет переменную с данным инициализатором, а затем захватывает ее. Я не уверен, как это вписывается в приведенное выше (объявление не может быть подвыражением) и будет ли это считаться использованием odr.
Хм, имеет смысл, чтобы разница была в непосредственном контексте или нет.
Может добавить тег language-lawyer