I was surprised to find that GCC and Clang disagree on whether to give me a linker error when passing a static constexpr member by value when there is no out-of-class definition:
#include <iostream>
#include <type_traits>
#include <typeinfo>
template <typename X>
void show(X)
{
std::cout << typeid(X).name() << std::endl;
}
template <typename T>
struct Foo
{
//static constexpr struct E {} nested {}; // works in gcc and clang
//static constexpr struct E {T x;} nested {}; // doesn't work in gcc
//static constexpr enum E {} nested {}; // works in gcc and clang
//static constexpr enum E { FOO } nested {}; // works in gcc and clang
//static constexpr struct E { constexpr E() {} constexpr E(const E&) {} T x=T();} nested {}; // works in gcc and clang
static constexpr struct E { constexpr E() {} constexpr E(const E&) = default; T x=T(); } nested {}; // doesn't work in gcc
};
int main()
{
Foo<int> x;
show(x.nested);
}
Snippet can be played with here.
I would like to use the first line's syntax with no out-of-class definition:
static constexpr struct E {} nested {}; // works in gcc and clang
When there are no members in E
, Clang and GCC only seem to care that I have no out-of-class definition of nested
if I trip ODR (e.g. by taking the address). Is this standard mandated or luck?
When there are members, GCC (5.2) appears to additionally want me to have manually defined a constexpr copy constructor. Is this a bug?
From googling and SO I have found several different answers but it's hard to tease apart which are up to date with C++14. In C++98/03 I believe only integer types could be initialized inside the class. I think that C++14 expanded this to 'literal' types which includes constexpr constructable things. I don't know if this is the same as saying I am allowed to get away with not having an out-of-class definition though.
So in the cases where E is a class they all looks like odr violations, if we look at cppreferences page on odr-use it says:
Informally, an object is odr-used if its address is taken, or a reference is bound to it, and a function is odr-used if a function call to it is made or its address is taken. If an object or a function is odr-used, its definition must exist somewhere in the program; a violation of that is a link-time error.
and in this case we take a reference when we call the copy constructor on this line:
show(x.nested);
It is also worth it to note that odr-violations do not require a diagnostic.
It looks what you are seeing in some cases in the effects of constructor elision with gcc if we use -fno-elide-constructors we get an error for all the cases where E is a class. In the enum cases the lvalue-to-rvalue conversion is applied and therefore there is no odr-use.
Update
dyp pointed me to defect report 1741 which questions whether binding to the reference parameter of a copy ctor is an odr-use or not:
Does this odr-use T::s, requiring it to have a definition, because of binding it to the reference parameter of S's copy constructor?
and the result was the following change to [basic.def.odr] paragraph 3:
A variable x whose name appears as a potentially-evaluated expression ex is odr-used unless
x satisfies the requirements for appearing in a constant expression (5.20 [expr.const])applying the lvalue-to-rvalue conversion (4.1 [conv.lval]) to x yields a constant expression (5.20 [expr.const]) that does not invoke any non-trivial functions and, if x is an object, ex is an element of the set of potential results of an expression e, where either the lvalue-to-rvalue conversion (4.1 [conv.lval]) is applied to e, or e is a discarded-value expression (Clause 5 [expr]). this is odr-used...
So then the question becomes which cases are covered by this change. It would seem these examples are ok:
//static constexpr struct E {} nested {}; // works in gcc and clang
//static constexpr struct E {T x;} nested {}; // doesn't work in gcc
static constexpr struct E { constexpr E() {} constexpr E(const E&) = default; T x=T(); } nested {}; // doesn't work in gcc
Since the copy ctor is trivial while this one is not trivial:
//static constexpr struct E { constexpr E() {} constexpr E(const E&) {} T x=T();} nested {}; // works in gcc and clang
We can confirm this using std::is_trivially_copyable, see it live.
The enum cases are still ok for the same reasons I stated originally.
The defect also report variance in implementation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With