7
votes

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?

2
@RichardHodges, -std=c++14. - Evg
OK now I think this is a bug of gcc and clang as c++14 doesn't come with constexpr lambda... this is available since c++17 - W.F.
@W.F., the lambda is intentionally not constexpr (in real code it isn't). - Evg
MSVC also complains: "failure was caused by read of a variable outside its lifetime" and g++ and clang conplain about the unused variable. If you return from the lambda, they compile. - Bob__
@Bob__ f(x) should be used in the context where constant expression is required. When you do return f(x), you do not force evaluation of f(x) at compile time. - Evg

2 Answers

5
votes

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.

2
votes

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.