Re: Multiple definition and specialization of static data member
Greg Herlihy wrote:
Wu Yongwei wrote:
I posted the following message days ago to comp.std.c++, and
got an helpful answer from Alberto Ganesh Barbati. However,
I would like to hear more comments from you gurus here, esp.
whether the behaviour of MSVC 8 is standard conforming, best
with quotations from the Standard.
-------------------------------
I encountered a problem in a header file like the following:
template <typename DataType>
class FMC
{
public:
static DataType Epsilon;
private:
FMC() {}
};
template <typename DataType>
DataType FMC<DataType>::Epsilon = static_cast<DataType>(0.000001);
This is the definition of Epsilon, a static data member of the
class template FMC. Placing this definition in a header file
is likely to prove problematic and for the reasons described.
Why? It's not problematic at all, and it didn't cause him any
problems. The standard says it has to work (?3.2/5), and I
don't know of a modern compiler where it doesn't work.
Essentially every source file that instantiates Epsilon will
also define it - leading to a multiply-defined symbol error at
link time.
It's true that every source file which instantiates Epsilon will
also define it. That's the implementation's problem, however,
and not yours ; the same thing holds for the constructor, and
presumable, the implementation will use the same mechanisms here
as it does for member functions.
template <>
double FMC<double>::Epsilon = static_cast<double>(0.0000000001);
This statement is an explicit specialization for Epsilon when
FMC is instantiated with the type double. Note that the
presence of an initializer for Epsilon makes this statement a
definition - and not a declaration - of the explicit
specialization. Once again, placing a definition in a header
file is bound to to lead a multiply-defined symbol error if
more than one file references FMC<double>::Epsilon.
In this case, there is a problem, because the definition is NOT
a definition of a "static data member of a class template"
(which is covered in ?3.2/5), and so may only be defined once.
The problem is that it isn't clear from the standard (to me, at
least) what the correct solution is.
When multiple .cpp files include this header file, GCC 3.4.4
(or some earlier version) will complain:
...: multiple definition of `FMC<double>::Epsilon'
...
To make GCC work, I have to reorganize the header file as follows:
- In header file
template <typename DataType>
class FMC
{
public:
static DataType Epsilon;
private:
FMC() {}
};
template <typename DataType>
DataType FMC<DataType>::Epsilon = static_cast<DataType>(0.000001);
Well, this definition staying in the header file can mean only
one thing: and that is that we probably have not seen the last
of the multiply-defined symbol error.
Only if you have a broken compiler. Unless the template has
been exported, this definition *MUST* be present in any
translation unit which uses the object. (If the template has
been exported, this definition should be in the implementation
source file of the template. If the template has been exported,
however, he will probably have problems compiling the code with
VC++ or g++.)
template <>
double FMC<double>::Epsilon;
This line in contrast has changed - and changed for the
better. Removing the initializer has transformed an explicit
specialization definition into an explicit specialization
declaration.
Sorry. According to ?9.4.2, it's a definition. In order for it
not to be a definition, you must declare it extern. It's not
fully clear, however, that extern is legal here, and some
compilers do reject it.
Note that multiple definitions in this case is undefined
behavior. The compiler can reject it, but it can also work.
Apparently, here, it works if there is no initializer, and
doesn't if there is one.
And the declaration has the opposite affect of the definition.
It inhibits the instantiation of the FMC<double>::Epsilon in
source files that reference it - unless that source file has
FMC<double>::Epsilon's definition.
Both the declaration and the definition inhibit the implicit
instatiation.
So moving the definition of the explicit specialization into a
source file ensures that the program defines the static data
member only once - no matter how many source files actually
reference it.
The problem is that the standard doesn't really provide a means
of just declaring a static data member of a class outside of the
class definition. And it doesn't allow explicit instantiation
of a static data member of a template class inside the class
definition. Unless extern is legal on the declaration outside
of the class, there is no real solution, as far as the standard
is concerned.
- In another .cpp file
#include "fmc.h"
template <>
double FMC<double>::Epsilon = static_cast<double>(0.0000000001);
However, then MSVC 8 will choke and declare that the
definition in the .cpp file is a redefinition. It works with
the original version.
The original version has undefined behavior, so it's acceptable
that it works; the compiler is not required to signal the error
(although it is allowed to). This version does have an error
requiring an error message. (Technically, if "fmc.h" is
included in more than one translation unit, the program has
undefined behavior, and the compiler is off the hook.
Presumably, however, if g++ accepts this, it will also accept
the program if this translation unit contains main(), and is the
only translation unit in the program. If it does so without a
diagnostic, it is an error in the compiler.)
My question is, which way is the standard-conforming way?
and which compiler is standard conformant on this issue?
Before we can judge the compilers, we need to get the program
into a working state.
In order to get the program into a working state, we have to
know what "a working state" is. In this case, the standard
makes a number of requirements, but it's not 100% clear that
there is a standard conforming solution.
And the structure of this program as we last left it, has only
a very limited fix for the Epsilon's multiple definition
problem. In fact it only works in one particular Epsilon - the
Epsilon for FMC<double>.
Just the contrary. If he removes the explicit instantiation of
FMC<double>::Epsilon, the program works for all possible
instantiations. Of course, the value of FMC<double>::Epsilon
will be 0.0, which isn't what he wants.
So as matters stand, if the program tries to instantiate FMC
with a type other than double, and then references that
template's Epsilon in more than once source file - than the
exact same problem would happen again.
Have you actually tried this? Or used templates in an actual
program, for that matter?
--
James Kanze GABI Software
Conseils en informatique orient?e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S?mard, 78210 St.-Cyr-l'?cole, France, +33 (0)1 30 23 00 34
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]