重载运算符<<,用于模板类的派生类

时间:2018-12-05 11:16:05

标签: c++ c++11 templates polymorphism operator-overloading

一些上下文: 我正在编写一个序列化库,并希望将要序列化的对象的所需更改降至最低。我找到了一些很棒的示例,例如MetaStuff,但想自己为模板练习和自定义实现它。

我的问题:

我想为派生类重载operator <<。 派生类继承自基类,而基类是它们自身的专用模板(通过CRTP)。 到目前为止,我要输出的成员变量是公共的,因此让我们无需声明操作员朋友。

template<typename Class>
Class Base {

protected:
    Base(std::string name) : name(name){}

public:
    const std::string name;

    static int f(const Class& instance){
        return instance.a;
    }
};

(我简化了很多步骤,只保留必要的元素)

struct Derived : public Base<Derived>{

    Derived():Base<Derived>("Derived"){}

    int a;

    static bool registerClass(){ return true; //called by Base<Derived> }
};

我遇到了3个冲突的问题(取决于我尝试的解决方案):

问题1:模版解析不明确

template <typename Class>
std::ostream& operator<<(std::ostream& os, const Class& obj)
{
    os << obj.name << "[ ";
    os << Base<Class>::f(obj);
    os << "]";

    return os;
};

在这里,我遇到了模棱两可的过载问题。模板函数具有与通用函数完全相同的原型。 Code & Compile error

问题2:类型切片

template <typename Class>
std::ostream& operator<<(std::ostream& os, const Base<Class>& obj)
{
    os << obj.name << "[ ";
    os << Base<Class>::f(obj); 
    //will not compile, since obj is not a Class object anymore.
    os << "]";

    return os;
};

如注释中所述,对象在传递给函数时被切片,然后我无法将其作为静态Base函数f的参数传递。 Code & Compile error

问题3:功能模板专业化不正确

不可能对模板函数进行部分专业化,并且要避免整体对函数模板进行专业化:编译器将始终倾向于使用Base模板。 (请参阅此不错的article

还剩下什么?

我正在考虑将解决方案2与static_cast一起使用,但会觉得很丑吗?我尝试了其他一些解决方案,这些都不值得一提。

还有其他线索吗?

(由于我希望对派生类的修改尽量少,所以我不想添加虚函数等)

2 个答案:

答案 0 :(得分:2)

正确的解决方案是尝试1和2中混合使用代码。

template <
    typename Class,
    typename = typename std::enable_if< std::is_base_of<Base<Class>, Class>::value >::type
>
std::ostream& operator<<(std::ostream& os, const Class& obj)
{
    os << obj.name << "[ ";
    os << Base<Class>::f(obj);
    os << " ]";

    return os;
}
  • 您需要使用Class&而不是Base<Class>&,因为您(可能)想对Class的成员进行操作。
  • 但是,使用enable_if是为了使运算符仅适用于从Base派生的类型;这样可以防止它蔓延到其他重载(通常,重载和模板混合不良),因为这样的模板重载可能会匹配任何内容。
  • 我们还明确需要将Class传递给Base,以获得正确的f

还有一个小字条;使用现代的编译器,您可以将其拼写得更好:

typename = std::enable_if_t<std::is_base_of_v<Base<Class>, Class>>

答案 1 :(得分:1)

CRTP通常使用static_cast

template <typename T>
class Base
{
protected:
    explicit Base(std::string name) : name(std::move(name)){}

    const T& asDerived() const { return static_cast<const T&>(*this); }
    T& asDerived() { return static_cast<T&>(*this); }

public:
    const std::string name;

    static int f(const Base<T>& instance){
        return instance.asDerived().a;
    }
};

等等

template <typename T>
std::ostream& operator<<(std::ostream& os, const Base<T>& obj)
{
    os << obj.name << "[ ";
    os << Base<T>::f(obj);
    os << "]";

    return os;
}

请注意,Base::f将不再是static

template <typename T>
class Base
{
    // ...
    int f() const { return asDerived().a; }
    // ...
};
template <typename T>
std::ostream& operator<<(std::ostream& os, const Base<T>& obj)
{
    return os << obj.name << "[ " << obj.f() << "]";
}