Overriding a templated function with a polymorphic one

假如想象 提交于 2019-12-12 10:57:39

问题


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 of dispatch
  • when resolving the result of dispatch, dispatch(TalkySerializable const&) is a better match than dispath(...), thus the return type is specialized
  • the generic serialize cannot be used (there is no conversion from specialized to generic), 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!