【问题标题】:writing template for operator<< for any vector为 operator<< 为任何向量编写模板
【发布时间】:2017-07-12 19:46:08
【问题描述】:

我正在尝试为任何可迭代容器编写模板运算符。收到一个奇怪的错误:

#include <iostream>

template <typename C>
std::ostream& operator<<(std::ostream& os, const C& c) {
  os << "[";
  for (const auto& v : c) {
    os << v << " ";
  }
  os << "]";
  return os;
}

vec.cc:5:6: 错误:重载运算符 '

为什么会出现这个错误?以及如何实现我想要的?

【问题讨论】:

  • @Curious 抱歉,已修复。
  • @tobi303 很好,我添加了#include
  • @MK。好吧,它仍然只是一个模板,但不会执行任何操作。至少你需要实例化它
  • 我认为这个全局级别的运算符会产生很多类型的歧义。 operator 的定义

标签: c++ c++11 templates


【解决方案1】:

添加

template <typename C>
std::ostream& operator<<(std::ostream& os, const C& c) {
  os << "[";
  for (const auto& v : c) {
    os << v << " ";
  }
  os << "]";
  return os;
}

operator &lt;&lt; 中的另一个global overloads 冲突。

为了解决这个问题,我们可以将您的模板限制为任何向量,而不是使用任何类型

template <typename C>
std::ostream& operator<<(std::ostream& os, const std::vector<C>& c) {
  os << "[";
  for (const auto& v : c) {
    os << v << " ";
  }
  os << "]";
  return os;
}

【讨论】:

    【解决方案2】:

    在这一行:

    os << "[";
    

    编译器找到两个有效的函数:STL 的和你的。

    为了解决冲突,您需要在模板声明中更具体一点:

    template <typename C>
    std::ostream& operator<<(std::ostream& os, const std::vector<C>& c) {
      ...
    

    一般来说,将其扩展到容器需要对std::enable_if&lt;&gt; 进行一些修改,这可能会让您更加困惑。我建议您只为要支持的每种类型的容器添加一个重载。

    编辑:另外,对于您不拥有的类型,覆盖 ostream &lt;&lt; T 通常是一个坏主意,因为它最终会导致冲突。

    【讨论】:

      【解决方案3】:

      发生错误是因为您的operator&lt;&lt; 与标准库中的许多其他重载相匹配。在您的情况下,过载意味着与const char[2] 一起使用。

      如果您希望它与任何可迭代容器一起使用,一种方法是限制它检查 begin()end() 方法的有效性

      #include <iostream>
      #include <vector>
      
      using std::cout;
      using std::endl;
      
      template <typename Container,
                std::enable_if_t<std::is_same<
                  decltype(std::declval<Container>().begin()),
                  decltype(std::declval<Container>().begin())>::value>* = nullptr>
      std::ostream& operator<<(std::ostream& os, const Container& container) {
          os << "[";
          for (const auto& ele : container) {
              os << ele << " ";
          }
          os << "]";
          return os;
      }
      
      int main() {
          auto vec = std::vector<int>{1, 2, 3};
          cout << vec << endl;
      }
      

      【讨论】:

      • 你好像输入了两次begin
      • 检查其上 begin() 和 end() 方法的有效性 ...容器不需要提供 成员 begin()end() 可迭代。
      • @zett42 这是此代码使用的基于范围的 for 循环的要求。
      • @zett42 啊没关系,这是std::beginstd::end 工作的要求。
      • @GuillaumeRacicot 我无法检查std::beginstd::end,因为这也适用于常规数组,并且会导致冲突过载
      【解决方案4】:

      您收到此错误是因为您的函数将每次调用 &lt;&lt; 与左侧的 std::ostream&amp; 匹配。

      template <typename C>
      std::ostream& operator<<(std::ostream& os, const C& c) {
        os << "[";
        for (const auto& v : c) {
          os << v << " ";
        }
        os << "]";
        return os;
      }
      

      当你写os &lt;&lt; "["时,编译器会找到多个operator&lt;&lt;函数来调用;你的就是其中之一。通过添加一个全局operator&lt;&lt;,它被模板化为任何类型,你基本上拦截了对operator&lt;&lt;的每个调用。

      最简洁的方法是定义一个新函数,比如print_collection

      template <typename C>
      void print_collection(std::ostream& os, const C& c) {
        os << "[";
        for (const auto& v : c) {
          os << v << " ";
        }
        os << "]";
      }
      

      如果您真的想要定义operator&lt;&lt;,这会变得更加棘手。你可以这样做:

      template <typename C>
      std::ostream& operator<<(std::ostream& os, const std::vector<C>& c) {
        os << "[";
        for (const auto& v : c) {
          os << v << " ";
        }
        os << "]";
        return os;
      }
      

      但是,如果标准库决定为std::vector 添加自己的operator&lt;&lt;,您的代码将会中断。

      我强烈建议,如果您想添加这样的operator&lt;&lt;,请为您自己的类型添加。像这样的:

      template <typename Iter>
      class Range {
          Iter begin_;
          Iter end_;
      
      public:
          Range() = default;
          Range(Iter begin, Iter end)
              : begin_{ begin }
              , end_{ end }
          {}
      
          auto begin() const { return begin_; }
          auto end() const { return end_; }
      };
      
      template <typename Iter>
      auto range(Iter begin, Iter end) {
          return Range<Iter>{ begin, end };
      }
      
      template <typename C>
      auto range(const C& collection) {
          return range(std::begin(collection), std::end(collection));
      }
      
      template <typename Iter>
      std::ostream& operator<<(std::ostream& os, const Range<Iter>& range) {
          os << "[";
          for (const auto& v : range) {
              os << v << " ";
          }
          os << "]";
          return os;
      }
      

      那么你可以这样使用它:

      std::vector<int> vec = ...;
      std::cout << range(vec);
      

      【讨论】:

        【解决方案5】:

        我相信您对运算符

        我会尝试使用一些 SFINAE 技术来更改您的原型,例如:

        template <typename C>
        std::ostream& operator<<(std::ostream& os, const C& c, C::const_iterator fakeVar = c.begin() ) {
          os << "[";
          for (const auto& v : c) {
            os << v << " ";
          }
          os << "]";
          return os;
        }
        

        它应该使您的模板只有在可以编译的情况下才适用

        【讨论】:

          【解决方案6】:

          正如其他人所解释的,对于os &lt;&lt; "[",您的运营商会引入歧义。

          我不知道这是否是个好主意,但我建议使用 SFINAE 使您的运算符仅用于支持 begin() 的类型。

          我是说

          template <typename C>
          auto operator<<(std::ostream& os, const C& c)
             -> decltype( c.begin(), os ) {
            os << "[";
            for (const auto & v : c) {
              os << v << " ";
            }
            os << "]";
            return os;
          }
          

          【讨论】:

          • 我不知道这是不是个好主意,但我建议 ...那你为什么还要写这个?
          • 这是什么 -> decltype 语法?
          • @zett42 - 我不知道为通用容器重新定义 operator&lt;&lt; 是否是个好主意
          • @MK - 你的意思是auto [...] -&gt; decltype() 还是decltype() 里面的逗号技巧?
          • @MK。 - 是两个不同的东西; auto [...] -&gt; type 是一种新的 C++11 定义函数返回类型的方法;在这种情况下,类型为decltype( os ),即“os 的类型”,即std::ostream &amp;。你可以问:你为什么不简单写std::ostream &amp; 而不是auto (等等)。答:因为我想使用 SFINAE(替换失败不是错误)。 (继续)
          【解决方案7】:

          歧义来自这样一个事实,即您的模板将operator&lt;&lt; 重载为std::ostream,并且可以使用多个定义。

          实际上,例如,标准库定义:

          std::ostream& operator<<(std::ostream&, const char*);
          

          您的函数将与模板替换 typename C = const char* 匹配。

          为了解决这种歧义,您可以采用另一种设计选择(例如,避免重载operator&lt;&lt;)或解决专门用于模板函数的歧义。

          在这种情况下,许多其他答案可能很好,我建议您使用另一种选择(不那么冗长,但有一些限制。见下文)。

          由于您尝试打​​印 Containers,因此容器很可能是一个模板类(std::vector&lt;T&gt;std::list&lt;T&gt; 等...)。因此,您可以稍微修改您的函数,以便仅处理模板类的类型。

          template <template <typename, typename...> class Container, typename T,
                    typename... Args>
          std::ostream& operator<<(std::ostream& os, const Container<T, Args...>& c) {
            os << "[";
            for (const auto& v : c) {
              os << v << " ";
            }
            os << "]";
            return os;
          }
          

          请注意,由于可变参数模板,代码需要C++11

          Here 一个例子表明歧义已得到解决。

          请注意,如前所述,该解决方案的局限性在于它仅适用于作为模板类的容器。它不适用于std::array,因为数组的结构是different

          【讨论】:

          • 您应该添加一个注释,它不适用于std::array 或任何非模板容器。
          • @GuillaumeRacicot 我写的适用于模板类的容器,但正如你所建议的,为了更清楚,我将添加否定句的否定句
          猜你喜欢
          • 2015-01-02
          • 1970-01-01
          • 2020-08-23
          • 1970-01-01
          • 1970-01-01
          • 2016-06-17
          • 2019-03-23
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多