Reputation: 6616
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
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
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?
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
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