Законно ли использовать std::declval в лямбда-выражениях в неоцененных контекстах?

Код, как показано ниже или на 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:

  1. в качестве аргумента лямбда-вызова: OK с clang/gcc/MSVC
  2. как лямбда-захват: OK с gcc/MSVC, ошибка с clang
  3. в теле лямбда: ошибка с clang/gcc/MSVC

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

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

Может добавить тег language-lawyer

463035818_is_not_a_number 18.11.2022 09:46

Похоже, это потому, что std::declval() не является constexpr функцией. См.: en.cppreference.com/w/cpp/utility/declval

Dmytro Ovdiienko 18.11.2022 09:51

Я не думаю, что static_assert это unevaluated context. Выражение в static_assert оценивается во время компиляции. std::declval() нельзя использовать в проверяемом контексте — это ошибка.

Dmytro Ovdiienko 18.11.2022 09:57

Удали static_assert и та же ошибка с clang: godbolt.org/z/MGzhMd19M

wanghan02 18.11.2022 09:59

Если вы удалите static_assert, вы получите лямбда. Лямбда — это оцененный контекст, не так ли?

Dmytro Ovdiienko 18.11.2022 10:00

Все дело в использовании std::declval() в лямбда-выражении в неоцениваемых контекстах, а std::declval() может использоваться только в неоцениваемых контекстах. На самом деле не имеет значения, является ли std::decval() constexpr или нет: godbolt.org/z/q6hfKEexf

wanghan02 18.11.2022 10:06

Кажется сомнительным, чтобы код сохранял результат declval даже в невычисленном контексте. Конкретная ошибка, с которой вы сталкиваетесь, находится в коде библиотеки, чтобы люди не пытались вызвать функцию. GCC выдает ту же ошибку, если я пытаюсь поместить if (std::declval<int>() == 0) в невычисленную лямбду.

chris 18.11.2022 10:11

IMO if (std::decval<int>()==0), очевидно, является серьезной ошибкой, а пример в посте - нет. Я просто хочу использовать самый простой пример, чтобы показать, в чем может быть проблема, даже если это немного глупо. Но я думаю, что это все еще допустимый вариант использования. Причина, по которой я придумал эту странную вещь, заключается в том, что у MSVC есть какая-то другая ошибка компилятора, если _ является параметром лямбда, и мы разрабатываем кроссплатформенное программное обеспечение.

wanghan02 18.11.2022 10:22

Случай if использует результат вызова функции точно так же, как ваш код использует его для инициализации захвата. Я не понимаю, чем они отличаются концептуально. Однако, если это вас не устраивает, auto x = std::declval<int>(); это то же самое, и это почти то же самое, что ваш захват делает за кулисами. В любом случае, это соответствует стандарту, чтобы справиться со всем этим.

chris 18.11.2022 10:29
Как настроить Tailwind CSS с React.js и Next.js?
Как настроить Tailwind CSS с React.js и Next.js?
Tailwind CSS - единственный фреймворк, который, как я убедился, масштабируется в больших командах. Он легко настраивается, адаптируется к любому...
LeetCode запись решения 2536. Увеличение подматриц на единицу
LeetCode запись решения 2536. Увеличение подматриц на единицу
Увеличение подматриц на единицу - LeetCode
Переключение светлых/темных тем
Переключение светлых/темных тем
В Microsoft Training - Guided Project - Build a simple website with web pages, CSS files and JavaScript files, мы объясняем, как CSS можно...
Отношения &quot;многие ко многим&quot; в Laravel с методами присоединения и отсоединения
Отношения &quot;многие ко многим&quot; в Laravel с методами присоединения и отсоединения
Отношения "многие ко многим" в Laravel могут быть немного сложными, но с помощью Eloquent ORM и его моделей мы можем сделать это с легкостью. В этой...
В PHP
В PHP
В большой кодовой базе с множеством различных компонентов классы, функции и константы могут иметь одинаковые имена. Это может привести к путанице и...
Карта дорог Беладжар PHP Laravel
Карта дорог Беладжар PHP Laravel
Laravel - это PHP-фреймворк, разработанный для облегчения разработки веб-приложений. Laravel предоставляет различные функции, упрощающие разработку...
8
9
170
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я не совсем уверен, как это должно работать, но вот моя попытка решения:

Согласно [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.

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

chris 19.11.2022 08:06

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