Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic lambda and its argument as constant expression

The following code is accepted by GCC 7.2 and clang 5.0.0, but is rejected by Microsoft VS 2017 15.5.0 Preview 5 and Intel C++ compiler 19:

struct S { };

constexpr int f(S)
{
    return 0;
}

int main()
{
    auto lambda = [](auto x)
    {
        constexpr int e = f(x);
    };

    lambda(S{});
}

Microsoft:

<source>(12): error C2131: expression did not evaluate to a constant

Intel:

<source>(12): error: expression must have a constant value
    constexpr int e = f(x);
                      ^
<source>(12): note: the value of parameter "x" (declared at line 10) cannot be used as a constant
    constexpr int e = f(x);
                        ^

If I replace f(x) with f(decltype(x){}), both Microsoft and Intel do not complain. I understand that x is not a constant expression, but it is not used inside f. This is probably why GCC and clang do not complain.

I guess that Microsoft and Intel compilers are correct in rejecting this code. What do you think?

like image 331
Evg Avatar asked Dec 07 '17 13:12

Evg


2 Answers

From [expr.const]:

An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine, would evaluate one of the following expressions:

  • [...]

  • an lvalue-to-rvalue conversion unless it is applied to

    • a non-volatile glvalue of integral or enumeration type that refers to a complete non-volatile const object with a preceding initialization, initialized with a constant expression, or
    • a non-volatile glvalue that refers to a subobject of a string literal, or
    • a non-volatile glvalue that refers to a non-volatile object defined with constexpr, or that refers to a non-mutable subobject of such an object, or
    • a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of e;
  • [...]

In f(x), we do an lvalue-to-rvalue conversion on x. x isn't of integral or enumeration type, it's not a subobject of a string-literal, it's not an object defined with constexpr, and its lifetime did not begin with the evaluation of f(x).

That seems to make this not a core constant expression.

However, as Casey points out, since S is empty, nothing in its implicitly-generated copy constructor would actually trigger this lvalue-to-rvalue conversion. That would mean that nothing in this expression actually violates any of the core constant expression restrictions, and hence gcc and clang are correct in accepting it. This interpretation seems correct to me. constexpr is fun.

like image 60
Barry Avatar answered Sep 26 '22 02:09

Barry


This is not a gcc/clang bug. The same behavior can be reproduced in C++11 with a template function:

template <typename T>
void foo(T x)
{
    constexpr int e = f(x);
}

int main()
{
    foo(S{});
}

on godbolt.org


The question is, given...

template <typename T>
void foo(T x)
{
    constexpr int e = f(x);
}

...is f(x) a constant expression?

From [expr.const]:

An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine, would evaluate one of the following expressions:

  • invocation of a function other than a constexpr constructor for a literal class, a constexpr function, or an implicit invocation of a trivial destructor

S{} and 0 are constant expressions because it doesn't violate any of the rules in [expr.const]. f(x) is a constant expression because it's an invocation to a constexpr function.

Unless I am missing something, gcc and clang are correct here.

like image 28
Vittorio Romeo Avatar answered Sep 26 '22 02:09

Vittorio Romeo