检查 << 运算符是否适用于覆盖模板上下文

问题描述

我创建了一个 AnyType 类,它类似于 C++14 中的 std::any。我想覆盖 << 运算符以轻松打印存储变量的内容

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::any 中。有谁知道它是如何在标准库中实现的?

干杯:)

解决方法

是的,一般来说这是可能的,但前提是您的类 AnyType 是模板类。在这种情况下,您可以直接使用 std::enable_if 禁用 operator<<。在您的情况下,AnyType 内的共享指针所持有的精确变量类型是在运行时决定的,您不能简单地使用 SFINAE 禁用它。您可以做的是在虚拟方法中使用类型特征来决定是否应该修改流,具体取决于VariableType的模板参数VariableHolder

只需定义一个 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 {
};

其中 S 对应于流(例如 std::ostream),T 对应于要检查以流式传输到 S 中的数据类型。

然后使用它在 VariableHolder::writeToStream 中禁用它来决定是否应该修改流。使用 C++17,您可以使用 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_;
};

Try it here!

C++11(根据要求)和 C++14 中,它有点复杂,您将不得不使用 helper 结构部分专门它用于 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_;
 };

Try it here!