Zizheng Tai
Zizheng Tai

Reputation: 6616

GCC and Clang different behaviors on constexpr constructor

For this struct:

struct Wrapper {
    int value;

    constexpr explicit Wrapper(int v) noexcept : value(v) {}
    Wrapper(const Wrapper& that) noexcept : value(that.value) {}
};

And this function:

constexpr Wrapper makeWrapper(int v)
{
    return Wrapper(v);
}

The following code fails to compile for Clang (Apple LLVM version 7.3.0), but compiles fine for GCC (4.9+), both with -Wall -Wextra -Werror -pedantic-errors:

constexpr auto x = makeWrapper(123);

Clang complains that "non-constexpr constructor 'Wrapper' cannot be used in a constant expression." Which compiler is right?

Upvotes: 14

Views: 1344

Answers (3)

Dietmar Kühl
Dietmar Kühl

Reputation: 153802

Although the copy or move when returning Wrapper from makeWrapper() can be elided, it is required to exist with C++14. The existing copy constructor is non-constexpr and its existence inhibits creation of an implicit move constructor. As a result I think clang is right: you'd need to make the copy constructor a constexpr.

Note that with C++17 the code might become correct: there is a proposal to make copy-elision mandatory in some contexts: P0135r0. However, it seems this change hasn't landed in the working paper, yet. It may land this week, though (thanks to @NicolBolas for pointing out that it isn't there, yet). I haven't seen an updated paper in the mailing.

Upvotes: 14

WhiZTiM
WhiZTiM

Reputation: 21576

Clang is correct. It works in g++ because it's automatically eliding the Copy constructor (RVO). if you pass -fno-elide-constructors. g++ will also complain.

The C++14 standard isn't clear about Copy-Elision in constexpr objects..

[class.copy/32] ...(partially reproduced here)

When the criteria for elision of a copy/move operation are met.... .... the selected constructor must be accessible even if the call is elided.

Until we know the definition of accessible? We can assume g++ is also correct?

dcl.constexpr/9

A constexpr specifier used in an object declaration declares the object as const. Such an object shall have literal type and shall be initialized. If it is initialized by a constructor call, that call shall be a constant expression ([expr.const]). Otherwise, or if a constexpr specifier is used in a reference declaration, every full-expression that appears in its initializer shall be a constant expression.

Dietmar Kuhl's answer tells us what's ahead.

Demo:

struct Wrapper {
    int value;

    constexpr explicit Wrapper(int v) noexcept : value(v) {}
    Wrapper(const Wrapper& that) noexcept : value(that.value)  {}
};

constexpr Wrapper makeWrapper(int v)
{
    return Wrapper(v);
}

int main()
{
    constexpr auto x = makeWrapper(123);
}

Compile with

g++ -std=c++14 -Wall -pedantic -fno-elide-constructors main.cpp && ./a.out

See it live here

Upvotes: 4

user743382
user743382

Reputation:

Both compilers are right.

The rules for constexpr functions and initialisers say that no non-constexpr function can be invoked.

The rules for copy elision say that it's unspecified whether the non-constexpr copy constructor gets invoked.

The only conclusion can be that it's unspecified whether the function and initialiser meet the requirements of constexpr. If they do, then the compiler must accept it. If they don't, then the compiler must diagnose the problem.

Upvotes: 3

Related Questions