用多态函数覆盖模板化函数

发布于 2024-12-09 16:58:04 字数 778 浏览 0 评论 0原文

如果我有

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将调用模板函数而不是覆盖函数

但是如果我专门定义了一个覆盖

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

那么gcc会选择那个。 有没有一种实用的方法可以用抽象类覆盖模板化函数?

我读过这篇文章,但它并没有阐明当你将多态性融入其中时会发生什么: http://www.gotw.ca/publications/mill17.htm 另外,我在这里找不到解决方案,但也许我使用了错误的术语。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

寂寞花火° 2024-12-16 16:58:04

定义函数TalkyBuffer&时运算符<<(TalkySerialisable const &object); 您没有覆盖。您正在重载 tmeplate 函数。

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

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

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.

以为你会在 2024-12-16 16:58:04

我认为可以使用一个简单的基于函数的解决方案,重用函数重载进行推导。

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

那么,会发生什么?

  • 当尝试解决 serialize 的重载时,将拾取 TalkyBuffer::operator<<(T const&)
  • ,它将首先计算 dispatch 的结果
  • 解析 dispatch 的结果时,dispatch(TalkySerialized const&)dispath(...),因此返回类型是 specialized
  • 不能使用通用 serialize(没有从 specialized 进行转换)generic),因此继承开始发挥作用

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
独夜无伴 2024-12-16 16:58:04

使用 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 提供插入运算符的新实现,需要提供一种新类型来充当标记(在我们的示例中为 TypeSerialized::tag),提供了一种将 T 与新标签关联的方法(通过使用示例中的嵌套 typedef,或者通过专门化特征类:template <> talky_buffer_trait{ typedef new_tag type };),最后重载实现函数(示例中的insertionOperatorImpl)。

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).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文