8

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;
}
Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
wanghan02
  • 1,227
  • 7
  • 14
  • maybe add the `language-lawyer` tag – 463035818_is_not_an_ai Nov 18 '22 at 08:46
  • Looks like that is because `std::declval()` is not a `constexpr` function. See: https://en.cppreference.com/w/cpp/utility/declval – Dmytro Ovdiienko Nov 18 '22 at 08:51
  • I do not think `static_assert` is a `unevaluated context`. The expression in the `static_assert` is evaluated at compile time. `std::declval()` cannot be used in the evaliated context - thus an error. – Dmytro Ovdiienko Nov 18 '22 at 08:57
  • remove static_assert and it's the same error with clang: https://godbolt.org/z/MGzhMd19M – wanghan02 Nov 18 '22 at 08:59
  • If you remove static_assert, you get lambda. Lambda is a evaluated context, no? – Dmytro Ovdiienko Nov 18 '22 at 09:00
  • 1
    The whole point is about using std::declval() in lambda in unevaluated contexts and std::declval() could only be used in unevaluated contexts. It is actually doesn't matter if std::decval() is constexpr or not: https://godbolt.org/z/q6hfKEexf – wanghan02 Nov 18 '22 at 09:06
  • It seems dubious to have code store the result of `declval`, even in an unevaluated context. The specific error you're running into is there in the library code to stop people from trying to actually call the function. GCC gives me the same error if I try to put `if (std::declval() == 0)` into an unevaluated lambda. – chris Nov 18 '22 at 09:11
  • IMO `if (std::decval()==0)` is obviously a hard error while the example in the post is not. I just want to use the simplest example to show what the problem might be even though it's a bit silly. But I think it is still a valid use case. The reason why I come up with this strange thing is that MSVC has some other compiler bug if _ is lambda parameter and we are developing a cross-platform software. – wanghan02 Nov 18 '22 at 09:22
  • 2
    The `if` case is using the result of calling the function, just like how your code is using it to initialize a capture. I don't see how they're different conceptually. If that doesn't float your boat, though, `auto x = std::declval();` is the same thing, and that's almost exactly what your capture is doing behind the scenes. Either way, it's up to the standard to handle all of these. – chris Nov 18 '22 at 09:29

1 Answers1

3

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.

user17732522
  • 53,019
  • 2
  • 56
  • 105