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?
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
extern
in a header and then without in a source file, orstatic
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.
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