I have made a AnyType
class which is similar to std::any in C++14. I would like to override the <<
operator to easily print the content of the stored variable:
std::ostream& operator<<( std::ostream& o, const AnyType& v ) {
if( v._varPtr_ != nullptr ) v._varPtr_->writeToStream(o);
return o;
}
And the writeToStream()
is an overrided function defined in a PlaceHolder
class which is holding the actual typed variable. It is defined as:
void writeToStream(std::ostream& o) const override { o << _variable_; }
My problem is when I define a AnyType
object which is holding a variable type with no operator<<
overrided, the compiler is throwing an (expected) error while unfolding a defined template class:
error: invalid operands to binary expression ('std::ostream' (aka 'basic_ostream<char>') and 'const TGraph')
void writeToStream(std::ostream& o) const override { o << _variable_; }
~ ^ ~~~~~~~~~~
Here is a minimal example:
struct PlaceHolder{
virtual ~PlaceHolder() = default;
virtual void writeToStream(std::ostream& o) const = 0;
};
template<typename VariableType> struct VariableHolder: public PlaceHolder{
explicit VariableHolder(VariableType value_) : _variable_(std::move(value_)){ }
void writeToStream(std::ostream& o) const override { o << _variable_; }
VariableType _variable_;
};
class AnyType{
public:
AnyType(){}
template<typename ValueType> inline void setValue(const ValueType& value_){
_varPtr_ = std::shared_ptr<VariableHolder<ValueType>>(new VariableHolder<ValueType>(value_));
}
friend std::ostream& operator<<( std::ostream& o, const AnyType& v ) {
if( v._varPtr_ != nullptr ) v._varPtr_->writeToStream(o);
return o;
}
private:
std::shared_ptr<PlaceHolder> _varPtr_{nullptr};
};
int main(){
AnyType a;
a.setValue(int(14));
std::cout << a << std::endl; // this will work
// If the following line is uncommented, the compiler will throw an error
// a.setValue(TGraph()); // this is a CERN's ROOT class
}
So my question is: Is there any way to make the compiler check if it is possible to override <<
before having it explicilty ? This way I could let the writeToStream
method to be defined as default: = 0
.
May be there is another workarround. Typically this problem does not appear with std::any
. Does anyone knows how it is implemented in the std library?
Cheers :)
Yes, generally speaking this is possible but only if your class AnyType
would be a template class. In this case you could disable the operator<<
directly with std::enable_if
. As in your case which precise variable type is held by the shared pointer inside AnyType
is decided on runtime you can't simply disable it with SFINAE. What you can do though is use a type-trait inside the virtual method to decide if the stream should be modified or not depending on the template parameter VariableType
of VariableHolder
:
Just define a type-trait is_streamable
as follows
template<typename S, typename T, typename = void>
struct is_streamable : std::false_type {
};
template<typename S, typename T>
struct is_streamable<S, T, decltype(std::declval<S&>() << std::declval<T&>(), void())> : std::true_type {
};
where S
corresponds to the stream (e.g. std::ostream
) and T
to the data type to be checked to be streamable into S
.
Then use it to disable it inside the VariableHolder::writeToStream
to decide whether the stream should be modified or not. With C++17 you could use if constexpr
:
template<typename VariableType>
struct VariableHolder: public PlaceHolder {
explicit VariableHolder(VariableType value_)
: _variable_(std::move(value_)) {
return;
}
void writeToStream(std::ostream& o) const override {
// Only for streamable variable types
if constexpr (is_streamable<std::ostream,VariableType>::value) {
o << _variable_;
}
return;
}
VariableType _variable_;
};
In C++11 (as requested) and C++14 it is a bit more complicated and you will have to use a helper struct and partially specialise it for true
(streamable types) and false
(non-streamable types) and then call it with our previously introduced type-trait is_streamable
as follows:
template <typename T, bool>
class helper {
};
// Partial specialisation for streamable variable types
template <typename T>
class helper<T,true> {
public:
static void imp(std::ostream& os, T const& t) {
// Stream variable t to stream os
os << t;
return;
}
};
// Partial specialisation for non-streamable variable types
template <typename T>
class helper<T,false> {
public:
static void imp(std::ostream&, T const&) {
// Leave stream os unmodified
return;
}
};
template<typename VariableType>
struct VariableHolder: public PlaceHolder {
explicit VariableHolder(VariableType value_)
: _variable_(std::move(value_)) {
return;
}
void writeToStream(std::ostream& o) const override {
// Call suiting helper function depending on template parameter
helper<VariableType, is_streamable<std::ostream,VariableType>::value>::imp(o, _variable_);
return;
}
VariableType _variable_;
};