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

Check if << operator is applicable in overriding template context

我做了一个 AnyType class 类似于 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 class 中定义的覆盖函数,它保存实际类型化的变量。它被定义为:

void writeToStream(std::ostream& o) const override { o << _variable_; }

我的问题是,当我定义一个 AnyType 对象,该对象持有一个没有 operator<< 覆盖的变量类型时,编译器在展开定义的模板时抛出一个(预期的)错误 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_; }
                                                         ~ ^  ~~~~~~~~~~

这是一个最小的例子:

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 通常不会出现此问题。有谁知道它在标准库中是如何实现的?

干杯:)

是的,一般来说这是可能的,但前提是您的 class AnyType 是模板 class。在这种情况下,您可以直接使用 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 禁用 operator<<。不过你可以做的是在虚拟方法中使用类型特征来决定是否应该修改流取决于[=18=的模板参数VariableType ]:

只需定义一个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!