Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

compiler problems with variable template

The following code (taken from Wikipedia) defines the variable template pi<>:

template<typename T=double>
constexpr T pi = T(3.14159265358979323846264338328);
template<>
constexpr const char* pi<const char*> = "π";

With the clang compiler (Apple clang version 12.0.0) (with C++14), this triggers a warning (with -Weverything):

no previous extern declaration for non-static variable 'pi<const char *>'

declare 'static' if the variable is not intended to be used outside of this translation unit

Moreover, since this was defined in a header, multiple instances of 'myNameSpace::pi<char const*>' were created, causing linker errors.

So, as suggested, I added the static keyword, which silenced the warning:

template<>
static constexpr const char* pi<const char*> = "π";

But now gcc (9.3.0) is unhappy, giving an error pointing at the static keyword:

error: explicit template specialization cannot have a storage class

What is the correct way to avoid either warning and error?

like image 448
Walter Avatar asked Feb 18 '21 18:02

Walter


Video Answer


1 Answers

The warning from (this old version of) Clang is partly misleading, but does indicate the real problem that you eventually encountered with the linker. The warning describes the good rule of thumb that a global variable ought to

  1. appear with extern in a header and then without in a source file, or
  2. appear with static in a source file (avoiding collisions with any other symbol).

The latter choice doesn't apply to explicit specializations: since linkage applies to templates as a whole (the standard says that it pertains to the name of the template, which is evocative even if it doesn't work well for overloaded functions), you can't make just one specialization static and Clang is incorrect to accept it. (MSVC also incorrectly accepts this.) The only way to make a "file-local specialization" is to use a template argument that is a local type, template, or object. You can of course make the whole variable template have internal linkage with static or an unnamed namespace.

However, the former choice does apply: an explicit specialization is not a template, so it must be defined exactly once (in a source file). Like any other global variable, you use extern to reduce the definition to a declaration:

// pi.hh (excerpt)
template<typename T=double>
constexpr T pi = T(3.14159265358979323846264338328);
template<>
extern constexpr const char* pi<const char*>;

// pi.cc
#include"pi.hh"
template<>
constexpr const char* pi<const char*> = "π";

(Since the primary template is, well, a template, it is defined in the header file.)

As mentioned in the comments, C++17 allows inline variables; your explicit specialization again behaves like an ordinary global variable and can be defined with inline in a header if desired.

like image 162
Davis Herring Avatar answered Sep 20 '22 00:09

Davis Herring