Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GCC and Clang different behaviors on constexpr constructor

Tags:

c++

c++11

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?

like image 253
Zizheng Tai Avatar asked Jun 20 '16 21:06

Zizheng Tai


People also ask

Are GCC and Clang interchangeable?

TL;DR: Clang is highly compatible to GCC - just give it a go. In most cases, Clang could be used as a GCC drop in replacement ( clang and clang++ are "GCC compatible drivers").

Can a constructor be Constexpr?

A constructor that is declared with a constexpr specifier is a constexpr constructor. Previously, only expressions of built-in types could be valid constant expressions. With constexpr constructors, objects of user-defined types can be included in valid constant expressions.

Can Constexpr throw?

Even though try blocks and inline assembly are allowed in constexpr functions, throwing exceptions or executing the assembly is still disallowed in a constant expression.


2 Answers

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.

like image 160
Dietmar Kühl Avatar answered Oct 08 '22 23:10

Dietmar Kühl


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

like image 41
WhiZTiM Avatar answered Oct 08 '22 21:10

WhiZTiM