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

Check if << operator is applicable in overriding template context

提问人:nadrino 提问时间:7/28/2021 最后编辑:nadrino 更新时间:7/28/2021 访问量:55

问:

我在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_; }

我的问题是,当我定义一个对象时,该对象包含未覆盖的变量类型,编译器在展开定义的模板类时会抛出(预期的)错误:AnyTypeoperator<<

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

干杯:)

C++11 模板 算符重 载重写 IOSTREAM

评论

0赞 2b-t 7/28/2021
您的意思是您正在寻找一种方法来检查是否可以流式传输到?在哪里定义?你能创建一个最小的、可重复的例子吗?__variable__std::ostream__variable__
0赞 nadrino 7/28/2021
是的,没错! 在结构下定义。我将尝试举一个最小的例子_variable_PlaceHolder
0赞 2b-t 7/28/2021
嗯,我明白了,但你想在运行时设置数据类型。这将取消使用模板和 SFINAE 的资格。如果模板类是固定的 ._varPtr_AnyTypeValueType
1赞 2b-t 7/28/2021
@TedLyngmo是的,我看到了你的解决方案。已经记下了几乎相同的解决方案。
0赞 nadrino 7/28/2021
我明白了,但是 std::any 设法在不明确定义为模板的情况下完成这项工作。我可以使用一些技巧吗?std::enable_if

答:

2赞 2b-t 7/28/2021 #1

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

只需按如下方式定义类型特征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 {
};

其中对应于流(例如)和要检查的数据类型,以便流式传输到 中。Sstd::ostreamTS

然后使用它在内部禁用它,以决定是否应修改流。使用 C++17 时,您可以使用 如果 constexprVariableHolder::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 中,它有点复杂,您将不得不使用一个辅助结构并将其部分专用于(可流类型)和(不可流类型),然后使用我们之前介绍的类型特征调用它,如下所示:truefalseis_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_;
 };

在这里试试吧!

评论

0赞 nadrino 7/28/2021
好!它几乎在我的程序中工作,但我仍然有一个问题:例如,我可以将返回类型更改为 bool,但随后编译器抛出警告,说使用 of 是 C++14。你有什么解决方法吗?Constexpr function's return type 'void' is not a literal typeos <<
1赞 2b-t 7/28/2021
@nadrino 也许你应该删除 .没有必要。我从我的解决方案中删除了它。让我知道这是否适合您,或者错误仍然存在。constexpr
0赞 nadrino 7/28/2021
多谢!它的工作原理是删除 !:Dconstexpr
0赞 2b-t 7/28/2021
@nadrino 不客气!很高兴我能帮上忙。我不确定它是否曾经在 C++11 中是这样的(现在主要使用 C++17 已经有一段时间了),或者它是否只是旧 GCC 编译器的编译器错误。