Is it legal to use std::declval in lambda in unevaluated contexts?

264 Views Asked by At

Code as below or on godbolt compiles with gcc and MSVC but fails with clang. I couldn't find if/where it is forbidden in the standard. In my opinion it should be supported.

So who is correct on this, clang or 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);
}

The example could be expanded into 3 cases as below or on godbolt:

  1. as lambda call argument: OK with clang/gcc/MSVC
  2. as lambda capture: OK with gcc/MSVC, error with clang
  3. in lambda body: error with clang/gcc/MSVC

So it seems clear that it is not legal in lambda body but legal outside as caller argument. It is not clear if it is allowed in capture list.

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

There are 1 best solutions below

1
On BEST ANSWER

I am not completely sure how this is intended to work, but here is my attempt at a solution:

According to [intro.execution]/3.3 the initializer of an init-capture is an immediate subexpression of a lambda expression. However none of the listed items make the expressions in the lambda's body subexpressions.

Unevaluated operands are non-potentially-evaluated expressions. But only their subexpressions are also non-potentially-evaluated expressions. (see [basic.def.odr]/2)

A function is odr-used if it is named by a potentially-evaluated expression or in some special cases that are not relevant here.

So std::declval should be fine in the init-capture initializer as in your point 2.

It is also ok in point 1, because the function arguments are subexpressions of the function call expression making up the whole unevaluated operand.

Point 3 is not ok, because the expressions in the body of the lambda are potentially-evaluated even if the lambda expression appears in an unevaluated operand.


One concern I have with the reasoning above is that [exp.prim.lambda.capture]/6 specified an init-capture to behave as if declaring a variable with the given intializer and then capturing it. I am not sure where this fits into the above (the declaration couldn't be a subexpression) and whether it would count as odr-use.