提问人:nadrino 提问时间:7/28/2021 最后编辑:nadrino 更新时间:7/28/2021 访问量:55
检查运算符<<是否适用于覆盖模板上下文
Check if << operator is applicable in overriding template context
问:
我在C++14中创建了一个类似于std::any的类。我想重写运算符以轻松打印存储变量的内容:AnyType
<<
std::ostream& operator<<( std::ostream& o, const AnyType& v ) {
if( v._varPtr_ != nullptr ) v._varPtr_->writeToStream(o);
return o;
}
它是在保存实际类型化变量的类中定义的重写函数。它被定义为:writeToStream()
PlaceHolder
void writeToStream(std::ostream& o) const override { o << _variable_; }
我的问题是,当我定义一个对象时,该对象包含未覆盖的变量类型,编译器在展开定义的模板类时会抛出(预期的)错误:AnyType
operator<<
error: invalid operands to binary expression ('std::ostream' (aka 'basic_ostream<char>') and 'const TGraph')
void writeToStream(std::ostream& o) const override { o << _variable_; }
~ ^ ~~~~~~~~~~
下面是一个最小的示例:
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
}
所以我的问题是:有没有办法让编译器在显式之前检查是否可以覆盖<<
?这样我就可以将方法定义为默认值:.writeToStream
= 0
可能还有另一个工作回合。通常,此问题不会出现在 中。有谁知道它是如何在 std 库中实现的?std::any
干杯:)
答:
是的,一般来说,这是可能的,但前提是您的类是模板类。在这种情况下,您可以直接使用 std::enable_if
禁用。就像您的情况一样,内部共享指针所持有的精确变量类型是在运行时决定的,您不能简单地使用 SFINAE 禁用它。不过,您可以做的是在虚拟方法中使用类型特征来决定是否应该修改流,具体取决于以下模板参数:AnyType
operator<<
AnyType
VariableType
VariableHolder
只需按如下方式定义类型特征is_streamable
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 {
};
其中对应于流(例如)和要检查的数据类型,以便流式传输到 中。S
std::ostream
T
S
然后使用它在内部禁用它,以决定是否应修改流。使用 C++17 时,您可以使用 如果 constexpr
:VariableHolder::writeToStream
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_;
};
在 C++11(根据要求)和 C++14 中,它有点复杂,您将不得不使用一个辅助结构并将其部分专用于(可流类型)和(不可流类型),然后使用我们之前介绍的类型特征调用它,如下所示:true
false
is_streamable
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_;
};
评论
Constexpr function's return type 'void' is not a literal type
os <<
constexpr
constexpr
评论
__variable__
std::ostream
__variable__
_variable_
PlaceHolder
_varPtr_
AnyType
ValueType
std::enable_if