【问题标题】:Overriding a templated function with a polymorphic one用多态函数覆盖模板函数
【发布时间】:2011-10-14 09:21:25
【问题描述】:

如果我有

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 我也在这里找不到解决方案,但也许我使用了错误的术语。

【问题讨论】:

  • b &lt;&lt; *static_cast&lt;TalkySerialisable*&gt;(&amp;test) 这行得通吗?

标签: c++ templates polymorphism template-specialization


【解决方案1】:

在定义函数TalkyBuffer&amp; operator&lt;&lt;(TalkySerialisable const &amp;object); 时,您没有覆盖。您正在重载 tmeplate 函数。

但是,当编译器看到b &lt;&lt; test; 时,它会搜索需要A 的运算符。它有一个,它是不需要自动转换的模板化函数。这是最好的选择。

重载函数需要对参数进行自动转换(从 A 到 TalkySerialisable)以适应声明,这不是最佳选择。

【讨论】:

  • 是的,他正在超载并且没有专攻。 template&lt;&gt; TalkyBuffer&amp; operator&lt;&lt; &lt;&gt;(TalkySerializable const &amp; object); 将是一个专业化。
  • 我同意卢克的观点。如果没有template&lt;&gt;,我会压倒一切。但是,您关于不需要“强制转换”优先的功能的观点当然很有趣,并解释了这种现象。我想知道有什么办法可以解决这个问题,同时保留与通用 TalkyBuffer&amp; operator&lt;&lt;(T const &amp;object) 等效的内容
  • @Elliot:好吧,确切地说,您不是覆盖,而是重载。重写是关于虚方法(动态多态性),而重载是关于拥有多个具有相同名称和不同参数的函数(这是另一种类型的多态性,但发生在编译时)。
  • @Luc:感谢准确的措辞。我更正了答案。
【解决方案2】:

我认为可以使用基于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&lt;&lt;(T const&amp;)将被接走
  • 当试图解决serialize 的过载时,它会首先计算dispatch 的结果
  • 解析dispatch的结果时,dispatch(TalkySerializable const&amp;)dispath(...)更匹配,因此返回类型为specialized
  • 不能使用通用的serialize(没有从specializedgeneric 的转换),所以继承开始

【讨论】:

  • 很好,使用重载函数来获取标签进行调度比类型特征要好得多,因为它可以无缝地与继承和隐式转换一起工作。
  • @LucTouraille:我只希望我可以让它不那么笨拙,但是如果我尝试将serialize 中的generic 参数直接替换为...,我会得到一个模棱两可的重载:x
【解决方案3】:

使用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 在评论中指出的那样,之前的解决方案的主要缺点是基础模板需要知道它将被重载,这是一种不必要的耦合,会阻碍可扩展性。

为了解决这个问题,我想出了一种使用tag dispatching 的新方法,以及使用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"
}

要为给定类型T 提供插入运算符的新实现,需要提供一个新类型作为标记(在我们的示例中为TypeSerializable::tag),提供一种将T 与新标签(在示例中使用嵌套 typedef,或者通过特化 trait 类:template &lt;&gt; talky_buffer_trait&lt;T&gt; { typedef new_tag type };),最后重载实现函数(示例中为insertionOperatorImpl)。

【讨论】:

  • 我更喜欢侵入性较小的方式。容量检测或特征将允许基本模板与存在特化的类型无关。
  • @Matthieu:是的,基本模板必须知道存在重载的事实也困扰着我。我会尝试提出更好的解决方案。
  • @Matthieu:问题在于模板和继承通常不能很好地协同工作。例如,如果我想使用一个 trait 类,我将无法为所有 TalkySerializable 专门化它,因为 trait&lt;A&gt; 与专门化不匹配。这是我经常偶然发现的一个问题,并且总是通过不完全让我满意的解决方法来解决,所以如果您对此主题有一些见解,我会很高兴听到他们的意见!
  • @Matthieu:我添加了另一个使用标签调度来减少耦合的解决方案,请随时分享您对这种其他方法的想法。
  • 我不知道有任何系统可以在没有基本模板配置的情况下工作,我担心的是反向依赖:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-10-22
  • 2018-05-05
  • 2014-08-16
  • 2014-03-08
  • 2010-10-25
  • 2021-11-08
  • 2017-06-28
相关资源
最近更新 更多