C++ 在模板化 class 上继承运算符 '<<'

C++ inherited operator '<<' on templated class

我有以下 C++ 代码,它们证明了我的问题。 我的目标是覆盖继承 class 中的流运算符,以便允许我根据对象类型打印特定流:

#include <iostream>
#include <unordered_set>

using namespace std;

template <typename T>
class Base {
    public:
        Base(){}
        Base(T n): value_(n){}

        friend inline ostream &operator<<(ostream &os, const Base &b) {
            b.to_str(os);
            return os;
        }

    protected:

        T value_;

        // All object should implement this function
        virtual void to_str(ostream& os) const {
            os << value_;
        }
};

template <typename T>
class Child: public Base<T> {
    public:
        Child(T n): Base<T>(n){}

    protected:
        void to_str(ostream& os) const override {
         os << "{";
            for (auto v = this->value_.begin(); v != this->value_.end(); v++) {
                if(v != this->value_.begin())
                    os << ",";
                os << (*v);
            }
            os << "}";
        }
};

int main()
{
    Base<string> b("base");
    Child<unordered_set<string>> c({"child"});
    cout << "b: " << b << endl;
    cout << "c: " << c << endl;

    return 0;
}

目前代码未编译:

main.cpp:31:16: error: no match for ‘operator<<’ (operand types are
‘std::ostream {aka std::basic_ostream}’ and ‘const std::unordered_set
>’)

编译器似乎使用了 base 中的虚方法 to_str(),而不是子 class 的重写方法。 如果我评论基础 to_str() 函数的主体,那么它会编译并打印 unordered_set Child class 的正确结果,但随后会打印与基本实现无关。 所以覆盖是有效的,但是为什么当 base to_str() 有一个主体时它不编译?

如何强制编译器使用派生的那个(子)?

此致

基础 to_str 会被编译,即使它永远不会被编译 运行。

所以

    virtual void to_str(ostream& os) const {
        os << value_;
    }

编译失败

当您创建一个带有 vtable 的类型时,它的条目(好吧,我相信标准说 "can be")被填充,即使它们没有被调用。这不同于 "normal" 模板 class,其中未使用的方法具有它们的主体 "skipped"。

您可能需要 CRTP 用于编译时多态性。

template <class T, class D_in=void>
class Base {
    using D=std::conditional_t< std::is_same<D_in,void>{}, Base, D_in >;
    public:
        Base(){}
        Base(T n): value_(n){}

        friend inline ostream &operator<<(ostream &os, const Base &b) {
            static_cast<D const&>(b).to_str(os);
            return os;
        }

    protected:

        T value_;

        // All object should implement this function
        void to_str(ostream& os) const {
            os << value_;
        }
};

template <typename T>
class Child: public Base<T, Child<T>> {
    friend class Base<T, Child<T>>;
    public:
        Child(T n): Base<T>(n){}

    protected:
        void to_str(ostream& os) const {
         os << "{";
            for (auto v = this->value_.begin(); v != this->value_.end(); v++) {
                if(v != this->value_.begin())
                    os << ",";
                os << (*v);
            }
            os << "}";
        }
};

或类似的东西。

虚拟调用确实发生了。您可以通过将代码更改为

来查看
template <typename T>
class Base {
    public:
        Base(){}
        Base(T n): value_(n){}

        friend inline ostream &operator<<(ostream &os, const Base &b) {
            b.to_str(os);
            return os;
        }

    protected:

        T value_;

        virtual void to_str(ostream& os) const = 0;
};

template <typename T>
class Child: public Base<T> {
    public:
        Child(T n): Base<T>(n){}

    protected:
        void to_str(ostream& os) const override {
         os << "{";
            for (auto v = this->value_.begin(); v != this->value_.end(); v++) {
                if(v != this->value_.begin())
                    os << ",";
                os << (*v);
            }
            os << "}";
        }
};

int main()
{
    Child<unordered_set<string>> c({"child"});
    cout << "c: " << c << endl;
    return 0;
}

实际发生的是,在 Base 中,编译器将淘汰

Base<unordered_set<string>>::to_str

并且该函数无效,因为 os << value_ 未定义。如您所见,您需要使其成为纯虚拟的,或者放入一个无论 value_ 是什么都可以编译的存根。

这是我最终使用 "pure virtual solution" 实现的, 测试了一些更基本的类型:

#include <iostream>
#include <unordered_set>

using namespace std;

template <typename T>
class Base_ {

    public:
        Base_(){}
        Base_(T n): value_(n){}

        friend inline ostream &operator<<(ostream &os, const Base_ &b) {
            b.to_str(os);
            return os;
        }

    protected:
        T value_;

        // All object should implement this function
        virtual void to_str(ostream& os) const = 0;
};

template <typename T>
class Base: public Base_<T> {
    public:
        Base(){}
        Base(T n): Base_<T>(n){}

    protected:

        // All object should implement this function
        void to_str(ostream& os) const override {
            os << this->value_;
        }
};

template <typename T>
class Child: public Base_<T> {
    public:
        Child(T n): Base_<T>(n){}

    protected:
        void to_str(ostream& os) const override {
         os << "{";
            for (auto v = this->value_.begin(); v != this->value_.end(); v++) {
                if(v != this->value_.begin())
                    os << ",";
                os << (*v);
            }
            os << "}";
        }
};

template <typename T>
class Boolean: public Base_<T> {
    public:
        Boolean(T n): Base_<T>(n){}

    protected:
        void to_str(ostream& os) const override {
         os << (this->value_ ? "true" : "false");
        }
};

int main()
{
    Base<string> s("string");
    Base<int> i(42);
    Boolean<bool> b(true);
    Child<unordered_set<string>> u({"child1", "child2"});
    cout << "s: " << s << endl;
    cout << "i: " << i << endl;
    cout << "b: " << b << endl;
    cout << "u: " << u << endl;

    return 0;
}

结果:

s: string
i: 42
b: true
u: {child2,child1}