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}
我有以下 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}