Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

constexpr array member with template specialization: inconsistent behavior cross compilers

Consider the following code:

#include <iostream>

template<class T>
struct foo {};

template<>
struct foo<int> {
  static constexpr char value[] = "abcde";
};

template<class T>
struct bar {
  static constexpr char value[] = "abcde";
};

template<class T>
struct baz {
  static constexpr int value = 12345;
};

int main() {
    char c = foo<int>::value[2];
    char d = bar<int>::value[2];
    int e = baz<int>::value;

    std::cout << c << d << e << "\n";
    
}

When compiling with: clang++ -std=c++14 ./test_foo.cc, I got linker error for undefined symbols: bar<int>::value and foo<int>::value. When I change to clang++ -std=c++17, then only one undefined symbol: foo<int>::value. My clang++ version is 5.0.

However, when I tried g++ -std=c++14 ./test_foo.cc, compile succeeded. My g++ version is 5.4.0.

I have precisely 2 things to ask.

1) From the C++ standard viewpoint, which compiler behaves correctly ?

I have googled and read many cppreference pages, but haven't found anything really related to this phenomenon. Especially for the clang++ with -std=c++17, the behave is really bizarre, since bar<int> got passed but foo<int> failed, the only difference is that foo<int> is a specialization. And I read from http://en.cppreference.com/w/cpp/language/constexpr that

A constexpr specifier used in a function or static member variable (since C++17) declaration implies inline.

Thus there seems no reason for template specialization foo<int> to fail. Furthermore, I looked at the generated object file before linking, the access to foo<int>::value[2]; is not done at compile time as one would expect it to be. I highly suspect there is something wrong in the clang++ compiler.

2) How to deal with this clang++ linking error ?

I tried something like Undefined reference to static constexpr char[], but finally I couldn't find any way to overcome this linking error. So I just wonder whether there is a way to get this linking succeed.

like image 962
llllllllll Avatar asked Jan 20 '18 22:01

llllllllll


1 Answers

Until C++17, the reason is exactly the same as what stated in the question you found (I think the answer posted by Shafik Yaghmour explains this problem more exactly). In short, the definition of a constexpr static data member is still required if the member is odr-used.

You can resolve the problem by providing definitions of these variable until C++17 (i.e. using -std=c++14).


Since C++17, the current standard in [dcl.constexpr] paragraph 1 says

... A function or static data member declared with the constexpr specifier is implicitly an inline function or variable ([dcl.inline]).

and in [basic.def] paragraph 2 says

A declaration is a definition unless

  • ...

  • it declares a non-inline static data member in a class definition ([class.mem], [class.static]),

  • ...

So such definitions at namespace scope are not required.

In addition, the current standard in [depr.static_constexpr] paragraph 1 says

For compatibility with prior C++ International Standards, a constexpr static data member may be redundantly redeclared outside the class with no initializer. This usage is deprecated. [ Example:

struct A {
  static constexpr int n = 5;  // definition (declaration in C++ 2014)
};

constexpr int A::n;  // redundant declaration (definition in C++ 2014)

— end example ]

So you'd better avoid such definitions since C++17.

When I change to clang++ -std=c++17, then only one undefined symbol: foo<int>::value.

It is a Clang bug. Anyway, it works well for Clang HEAD 7.0.0.

like image 155
xskxzr Avatar answered Oct 05 '22 20:10

xskxzr