I wanted to share a strange example with you guys that I stumbled upon and that kept me thinking for two days.
For this example to work you need:
- triangle-shaped virtual inheritance (on member function
getAsString()
) - member function specialization of a template class (here,
Value<bool>::getAsString()
) overriding the virtual function - (automatic) inlining by the compiler
You start with a template class that virtually inherits a common interface - i.e. a set of virtual functions. Later, we will specialize one of these virtual functions. Inlining may then cause our specilization to get overloooked.
// test1.cpp and test2.cpp
#include <string>
class ValueInterface_common
{
public:
virtual ~ValueInterface_common() {}
virtual const std::string getAsString() const=0;
};
template <class T>
class Value :
virtual public ValueInterface_common
{
public:
virtual ~Value() {}
const std::string getAsString() const;
};
template <class T>
inline const std::string Value<T>::getAsString() const
{
return std::string("other type");
}
Next, we have to inherit this Value
class and the interface in a Parameter
class that itself needs to be templated as well:
// test1.cpp
template <class T>
class Parameter :
virtual public Value<T>,
virtual public ValueInterface_common
{
public:
virtual ~Parameter() {}
const std::string getAsString() const;
};
template<typename T>
inline const std::string Parameter<T>::getAsString() const
{
return Value<T>::getAsString();
}
Now, do not(!) give the forward declaration of a specialization for Value
for type equaling bool ...
// NOT in: test1.cpp
template <>
const std::string Value<bool>::getAsString() const;
But instead simply give its definition like this ...
// test2.cpp
template <>
const std::string Value<bool>::getAsString() const
{
return std::string("bool");
}
.. but in another module (that's important)!
And finally, we have a main()
function to test what is happening:
// test1.cpp
#include <iostream>
int main(int argc, char **argv)
{
ValueInterface_common *paraminterface = new Parameter<bool>();
Parameter<int> paramint;
Value<int> valint;
Value<bool> valbool;
Parameter<bool> parambool;
std::cout << "paramint is " << paramint.getAsString() << std::endl;
std::cout << "parambool is " << parambool.getAsString() << std::endl;
std::cout << "valint is " << valint.getAsString() << std::endl;
std::cout << "valbool is " << valbool.getAsString() << std::endl;
std::cout << "parambool as PI is " << paraminterface->getAsString() << std::endl;
delete paraminterface;
return 0;
}
If you compile the code as follows (I placed it into two modules named test1.cpp and test2.cpp where the latter only contains the specialization and necessary declarations):
g++ -O3 -g test1.cpp test2.cpp -o test && ./test
The output is
paramint is other type
parambool is other type
valint is other type
valbool is bool
parambool as PI is other type
If you compile with -O0
or just -fno-inline
- or also if you do give the forward declaration of the specialization - the result becomes:
paramint is other type
parambool is bool
valint is other type
valbool is bool
parambool as PI is bool
Funny, isn't it?
My explanation so far is: Inlining is at work in the first module (test.cpp). The required template functions get instantiated but some just end up being inlined in the calls to Parameter<bool>::getAsString()
. On the other hand, for valbool
this did not work but the template is instantiated and used as a function. The linker then finds both the instantiated template function and the specialized one given in the second module and decides for the latter.
What do you think of it?
- do you consider this behavior a bug?
- Why does inlining work for
Parameter<bool>::getAsString()
but not forValue<bool>::getAsString()
although both override a virtual function?