Compiler Fights XIV: Doom of the Duplicitous Double Definition, co-starring The Dubious Declaration!
Compilers, all with either -O0
or Debug mode:
Summary:
template <> const std::string TemplatedClass::x; // .h file
template <> const std::string TemplatedClass::x= "string"; // .cpp file
While making an MCVE of a problem I was having with defining specialized static member variables of a template, I encountered an interesting variation in behavior between VC++, GCC and Clang with respect to the declaration said specialized static member variables. Specifically, the syntax
template <> const std::string TemplatedClass<int>::x; // .h file
template <> const std::string TemplatedClass<int>::x= "string"; // .cpp file
seems to mortally offend VC++, which responds with complaints of multiple definitions:
error C2374: 'member' : redefinition; multiple initialization
while both gcc and clang take this in stride.
I'm assuming the latter two are correct because they usually are, and also because the above syntax is from an answer regarding static member initialization of a specialized template class, which quotes paragraph 14.7.3/15 from the standard of 2010 in stating that template<> X Q<int>::x
is a declaration, not a definition. I took the liberty of tracking down the equivalent paragraph of draft N4296, thinking it could have changed in the intervening time. It has, but only in that it's moved two paragraphs up and contains additional clarification:
14.7.3/13
An explicit specialization of a static data member of a template or an explicit specialization of a static data member template is a definition if the declaration includes an initializer; otherwise, it is a declaration. [ Note: The definition of a static data member of a template that requires default initialization must use a braced-init-list:
template<> X Q<int>::x; // declaration template<> X Q<int>::x (); // error: declares a function template<> X Q<int>::x { }; // definition
— end note ]
This seems pretty clear to me, but VC++ seems to have a different interpretation. I've tried simply commenting out the offending declaration, and no compilers complain, which would seem to solve my troubles, but doesn't because paragraph 6 has this to say: (worrying emphasis mine)
14.7.3/6
If a template, a member template or a member of a class template is explicitly specialized then that specialization shall be declared before the first use of that specialization that would cause an implicit instantiation to take place, in every translation unit in which such a use occurs; no diagnostic is required. If the program does not provide a definition for an explicit specialization and either the specialization is used in a way that would cause an implicit instantiation to take place or the member is a virtual member function, the program is ill-formed, no diagnostic required. An implicit instantiation is never generated for an explicit specialization that is declared but not defined.
It provides examples, but all of them are for specializing functions after they're used or specializing member enums and classes of a templated type, which I'm fairly certain don't apply to this problem. However, the initial words of p13 seem to imply that the declaration of the specialized static member variable is also an explicit specialization, at least when using the illustrated syntax.
The test I used for my experimentation can be found on Coliru, with apologies to StackedCrooked for the fairly involved command line. A much shortened version is below:
main.cpp
#include <iostream>
// 'header' file
#include "test.h"
int main(){
std::cout << test::FruitNames<Fruit::APPLE>::lowercaseName();
}
test.h (declaration not commented out)
test.h (declaration commented out)
#ifndef TEMPLATE_TEST
#define TEMPLATE_TEST
#include <algorithm>
#include <locale>
#include <string>
namespace test{
enum class Fruits{
APPLE
};
template <Fruits FruitType_>
class FruitNames{
static const std::string name_;
/*...*/
public:
static std::string lowercaseName() {/*...uses name_...*/}
};
// should be counted as declaration. VC++ doesn't.
template <> const std::string FruitNames<Fruits::APPLE>::name_;
} // end namespace test
#endif // TEMPLATE_TEST
test.cpp
#include "test.h"
namespace test{
template <> const std::string FruitNames<Fruits::APPLE>::name_ = "Apple";
}
Both gcc and clang will output
apple
with or without the specialization declaration in test.h. VC++ will do so if the declaration in test.h is commented out, but will produce a double initialization error if it is present.
Is VC++ incorrect to reject the declaration/explicit specialization syntax for the static member variable of a templated class as previously stated, or is it an allowed but not mandatory diagnostic error?
Yes, this is a bug in VC++. It has apparently been fixed in Visual Studio 2019 version 16.5 Preview 2.
Does the removal of the declaration cause the program to be ill-formed?
Your quote from the standard seems to suggest that. Other people agree.
If it is ill formed without the declaration, how do I get VC++ to play nice with a well-defined program?
As a workaround, you can specialize the whole class and then define the member without the template<>
syntax. See Amir Kirsh's answer to a similar question:
https://stackoverflow.com/a/58583521/758345
Alternatively, you could define and initialize your variable in your header and mark it as inline (since c++17):
template <> inline const std::string TemplatedClass::x = "string"; // .h file
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