问题
If I have
template<class T>
TalkyBuffer& operator<<(T const &object) { // Template
...
}
TalkyBuffer& operator<<(TalkySerialisable const &object); // Override
and a class
class A : public TalkySerialisable {
...}
Then if I perform
TalkyBuffer b;
A test;
b << test;
Then gcc is calling the Template function rather than the Override function
However if I specifically define an override
TalkyBuffer& operator<<(A const &object); // Override without polymorphism
Then gcc picks that one. Is there a practical way to override a templated function with an abstract class?
I read this but it doesn't shed light onto what happens when you throw polymorphism into the mix: http://www.gotw.ca/publications/mill17.htm Also I couldn't find a solution here but perhaps I'm using the wrong terms.
回答1:
When defining the function TalkyBuffer& operator<<(TalkySerialisable const &object);
You are not overriding. You are overloading the tmeplated function.
But, when the complier sees b << test;
, it searches for an operator that wants an A
. It has one, it's the templated function that requires no automatic cast. This is the best choice.
The overloaded function requires an automatic cast (from A to TalkySerialisable) on the parameters to fit the declaration, and is not the best choice.
回答2:
I think it's possible to use a simple function
based solution, reusing function overload for derivation.
struct specialized {};
struct generic {};
template <class T>
TalkyBuffer& serialize(TalkyBuffer& buffer, T const& object, generic) {
...
}
generic dispatch(...) {} // always picked up last in overload resolution
template <class T>
TalkyBuffer& TalkyBuffer::operator<<(T const& object) { // Template
return serialize(*this, object, dispatch(object));
}
Now, let's implement your custom class:
TalkyBuffer& serialize(TalkyBuffer& buffer,
TalkySerialisable const& object,
specialized);
specialized dispatch(TalkySerialisable const&) {}
And create a derived one:
class A: public TalkySerialisable {};
So, what happens ?
TalkyBuffer::operator<<(T const&)
will be picked up- when trying to resolve the overload for
serialize
, it will first compute the result ofdispatch
- when resolving the result of
dispatch
,dispatch(TalkySerializable const&)
is a better match thandispath(...)
, thus the return type isspecialized
- the generic
serialize
cannot be used (there is no conversion fromspecialized
togeneric
), so inheritance kicks in
回答3:
A solution using Boost.enable_if:
#include <boost/utility/enable_if.hpp>
#include <boost/type_traits/is_base_of.hpp>
template<typename T>
typename boost::disable_if<
boost::is_base_of<TalkySerializable, T>,
TalkyBuffer &
>::type operator<<(T const & object) { // Template for non TalkySerializable
...
}
template <typename T>
typename boost::enable_if<
boost::is_base_of<TalkySerializable, T>,
TalkyBuffer &
>::type operator<<(T const & object); // Template overload for TalkySerializable
...
TalkyBuffer b;
A test;
b << test; // calls operator<< <A>(A const &), which instantiates
// the overload for TalkySerializable
b << 41; // calls operator<< <int>(int const &), which corresponds to
// the "default" overload
I'm not sure this is the best solution, but I failed to find a better one: specializing the template does not work either.
As @Matthieu noted in the comment, the previous solution has the major drawback that the base template needs to know that it will be overloaded, which is an unnecessary coupling that hinders extensibility.
To solve this problem, I came up with a new approach using tag dispatching, along with trait classes and compile-time introspection using Boost.MPL macros.
// TalkyBuffer.hpp
#include <iostream>
#include <boost/utility/enable_if.hpp>
#include <boost/mpl/has_xxx.hpp>
// defines a metafunction has_talky_buffer_tag<T> that allows us to know at
// compile-time if T has a member type named talky_buffer_tag
BOOST_MPL_HAS_XXX_TRAIT_DEF(talky_buffer_tag)
// tag for the default case
struct default_talky_buffer_tag {};
// trait class for obtaining the tag of a type
template <typename T, typename Enable = void >
struct talky_buffer_trait
{
typedef default_talky_buffer_tag type;
};
// specialization for types that provide a nested typedef
template <typename T>
struct talky_buffer_trait<T,
typename boost::enable_if<has_talky_buffer_tag<T> >::type>
{
typedef typename T::talky_buffer_tag type;
};
struct TalkyBuffer
{
// Insertion operator, which calls an implementation function that can
// be overloaded depending on the tag
template<typename T>
TalkyBuffer & operator<<(T const & object)
{
typename talky_buffer_trait<T>::type tag;
return insertionOperatorImpl(*this, object, tag);
}
};
// default implementation
template <typename T>
TalkyBuffer & insertionOperatorImpl(TalkyBuffer & buf, T const & object,
default_talky_buffer_tag)
{
std::cout << "default";
return buf;
}
//-------
// TalkySerializable.hpp
struct TalkySerializable
{
struct tag {};
typedef tag talky_buffer_tag;
};
// A inherits from the nested typedef
struct A : public TalkySerializable {};
// implementation for TalkySerializable objects
template <typename Serializable>
TalkyBuffer & insertionOperatorImpl(TalkyBuffer & buf, Serializable const & object,
TalkySerializable::tag)
{
std::cout << "specialized";
return buf;
}
//-------
int main()
{
TalkyBuffer b;
A test;
b << test; // outputs "specialized"
b << 41; // outputs "default"
}
To provide new implementations of the insertion operator for a given type T
, one needs to provide a new type to act as a tag (TypeSerializable::tag
in our example), provides a way to associate T
with the new tag (either by using a nested typedef as in the example, or by specializing the trait class: template <> talky_buffer_trait<T> { typedef new_tag type };
), and finally overload the implementation function (insertionOperatorImpl
in the example).
来源:https://stackoverflow.com/questions/7765562/overriding-a-templated-function-with-a-polymorphic-one