I have a snippet:
enum class EC {a, b};
struct B {
constexpr B(EC ec): ec_(ec) {}
EC ec_;
};
struct A_base {
constexpr A_base(B b): b_(b) { }
B b_;
};
struct A: A_base {
static constexpr B bbb = EC::a;
constexpr A(B bbbb): A_base(bbbb) { }
};
int main()
{
A a1(A::bbb); // 1
A a2{A::bbb}; // 2
A a3 = A::bbb; // 3
A a4 = {A::bbb}; // 4
}
It compiles good by modern compilers with c++17 support. With c++11 and c++14 standard support linker error occurs. This question was already discussed in Linker error (undefined reference) with `static constexpr const char*` and perfect-forwarding , C++ Linker Error With Class static constexpr and some other discussions. I understand why this error occurs.
However some things I don't understand:
Is it normal that compilers differ with turned off optimizations and why does gcc treats a1,a2 initializations not equally to a3, a4?
All the initializations of A
s invoke the A::A(B )
constructor, which require copying a B
, which uses the compiler-generated B::B(B const&)
. Binding a variable to reference (A::bbb
) is an odr-use of that variable. The specific rule, from [basic.def.odr] is:
A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion (4.1) to x yields a constant expression (5.20) 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) is applied to e, or e is a discarded-value expression (Clause 5).
The first call to the copy constructor would involve binding A::bbb
to a reference, which is neither an lvalue-to-rvalue conversion nor a discarded-value expression, so it's odr-used.
The most important rule is:
Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program outside of a discarded statement (6.4.1); no diagnostic required.
A::bbb
is odr-used, but lacks a definition, so we're violating that rule - commonly referred to as an odr violation. But since the compiler is not required to issue a diagnostic in this case ("no diagnostic required", or NDR for short), the program is undefined behavior. These kinds of issues can be frustrating at times for a programmer, but they're arbitrarily difficult for the compiler to diagnose - so it's something that we have to live with.
It is likely that on higher optimization levels, the compilers simply elide the copy, so a call to B::B(B const&)
for A::bbb
isn't necessary... As to why different initializations are treated differently? Probably as a result of different optimization passes. Ultimately, it doesn't change the fact that this is an odr violation - regardless of whether the code compiles and links.
As a result of p0386, static constexpr
data members are implicitly inline, which means that now A::bbb
does have a definition and now there is no odr-violation. C++17 is cool.
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