Reputation: 13
I have the following C++ code, which demonstrate my problem. My goal is to override the stream operator in inherited class in order to allow me to print specific stream depending on object type:
#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;
}
For now the code is not compiling:
main.cpp:31:16: error: no match for ‘operator<<’ (operand types are ‘std::ostream {aka std::basic_ostream}’ and ‘const std::unordered_set >’)
It seem that the virtual method to_str() from base is used by the compiler instead of the overrided one for the Child class. If I comment the body of the base to_str() function then it compile and print the correct result for unordered_set Child class but then it print nothing with the base implementation. So the override is working, but why it do not compile when base to_str() have a body?
How can I force the compiler to use the one from the derived one (Child)?
Regards
Upvotes: 1
Views: 80
Reputation: 13
This is what I finally implemented by using the "pure virtual solution", Tested with some more basic type:
#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;
}
Result:
s: string
i: 42
b: true
u: {child2,child1}
Upvotes: 0
Reputation: 180630
The virtual call actually does happen. You can see that by changing the code to
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;
}
What is actually happening is that in Base
the compiler is going to stamp out
Base<unordered_set<string>>::to_str
and that function is invalid since os << value_
is not defined. As you can see you need to either make it pure virtual, or put in a stub that will compile no matter what value_
is.
Upvotes: 0
Reputation: 275405
The base to_str
gets compiled even if it is never run.
So
virtual void to_str(ostream& os) const {
os << value_;
}
fails to compile.
When you create a type with a vtable, its entries are (well, I believe the standard says "can be") populated even if they aren't called. This is different than a "normal" template class, where unused methods have thier bodies "skipped".
It is possible you want the CRTP for compile-time polymorphism.
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 << "}";
}
};
or somesuch.
Upvotes: 1