Переопределение шаблонной функции полиморфной - PullRequest
5 голосов
/ 14 октября 2011

Если у меня есть

template<class T>
TalkyBuffer& operator<<(T const &object) { // Template
...
}
TalkyBuffer& operator<<(TalkySerialisable const &object); // Override

и класс

class A : public TalkySerialisable {
...}

Тогда, если я выполню

TalkyBuffer b;
A test;
b << test;

Тогда gcc вызывает функцию Template, а не функцию Override.

Однако, если я определю переопределение

TalkyBuffer& operator<<(A const &object); // Override without polymorphism

Тогда gcc выбирает это. Есть ли практический способ переопределить шаблонную функцию с помощью абстрактного класса?

Я читал это, но это не проливает свет на то, что происходит, когда вы добавляете в смесь полиморфизм http://www.gotw.ca/publications/mill17.htm Также я не мог найти решение здесь, но, возможно, я использую неправильные термины.

Ответы [ 3 ]

1 голос
/ 14 октября 2011

Я думаю, что можно использовать простое решение на основе function, повторно используя перегрузку функций для деривации.

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));
}

Теперь давайте реализуем ваш пользовательский класс:

TalkyBuffer& serialize(TalkyBuffer& buffer,
                       TalkySerialisable const& object,
                       specialized);

specialized dispatch(TalkySerialisable const&) {}    

И создайте производное:

class A: public TalkySerialisable {};

Итак, что происходит?

  • TalkyBuffer::operator<<(T const&) будет взято
  • при попытке разрешить перегрузку для serialize сначала вычисляется результат dispatch
  • при разрешении результата dispatch, dispatch(TalkySerializable const&) лучше, чем dispath(...), поэтому тип возвращаемого значения specialized
  • универсальный serialize не может быть использован (нет преобразования из specialized в generic), поэтому наследование запускается в
1 голос
/ 14 октября 2011

При определении функции TalkyBuffer& operator<<(TalkySerialisable const &object); Вы не переопределяете. Вы перегружаете функцию tmeplated.

Но, когда компилятор видит b << test;, он ищет оператора, который хочет A. У него есть один, это шаблонная функция, которая не требует автоматического приведения. Это лучший выбор.

Перегруженная функция требует автоматического приведения (от A к TalkySerialisable) параметров для соответствия объявлению, и это не лучший выбор.

0 голосов
/ 14 октября 2011

Решение с использованием 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

Я не уверен, что это лучшее решение, но я не смог найти лучшего: специализация шаблона также не работает.


Как отметил @Matthieu в комментарии, предыдущее решение имеет главный недостаток, заключающийся в том, что базовый шаблон должен знать, что он будет перегружен, что является ненужной связью, которая препятствует расширяемости.

Для решения этой проблемы я предложил новый подход, использующий диспетчеризацию тегов , наряду с классами признаков и самоанализом во время компиляции с использованием макросов Boost.MPL .

// 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"
}

Чтобы обеспечить новые реализации оператора вставки для данного типа T, необходимо предоставить новый тип, который будет действовать как тег (TypeSerializable::tag в нашем примере), предоставляет способ связать T с новый тег (либо с помощью вложенной typedef, как в примере, либо путем специализации класса черты: template <> talky_buffer_trait<T> { typedef new_tag type };), и, наконец, перегрузите функцию реализации (insertionOperatorImpl в примере).

...