为std::vector和std::list同时重载运算符。

7

我希望可以通过以下代码为 std::liststd::vector 重载 operator<<,但是这两个函数几乎相同。有没有办法将它们组合起来,也就是创建更通用的重载函数?

#include <iterator>
#include <iostream>
#include <vector>
#include <list>

template <typename T>
std::ostream &operator<<(std::ostream &out, const std::vector<T> &v)
{
  if (!v.empty())
    std::copy(v.begin(), v.end(), std::ostream_iterator<T>(out, ", "));
  return out;
}

template <typename T>
std::ostream &operator<<(std::ostream &out, const std::list<T> &v)
{
  if (!v.empty())
    std::copy(v.begin(), v.end(), std::ostream_iterator<T>(out, ", "));
  return out;
}

int main()
{
  std::cout << std::vector<int>({1, 2, 3, 4}) << std::endl;
  std::cout << std::list<int>({1, 2, 3, 4}) << std::endl;
  return 0;
}
2个回答

4
你可以像以下示例中使用具有模板参数的模板嵌套:
template <typename T, typename A, template <typename X, typename Y> class C> 
std::ostream &operator<<(std::ostream &os, const C<T,A> &container)
{
  if(!container.empty())
    std::copy(container.begin(), container.end(), std::ostream_iterator<T>(os, " "));
  return os;
}

int main() {
    list<int> l{1,2,3,4,5}; 
    vector<string> v{"one","two","three"};
    cout<<l<<endl<<v; 
    return 0;
}

在线演示

顺便提一下,在这个SO问题中,您可以找到其他使用模板的示例。

但是您必须小心这种结构:

  • 它仅适用于使用两个模板参数定义的容器(因此对于列表和向量而言很好,但不适用于集合或映射)。
  • 它可能与使用两个参数的其他模板类型发生冲突,而没有提取器的特殊化。

备注:如果您正在寻找通用解决方案,最好考虑创建一个使用迭代器作为参数的适配器模板,然后为该适配器编写通用提取器。


4

请进入C++20,可能会包含概念范围,你的问题的解决方案可以大大简化。

概念基本上是具有约束条件的模板参数,例如:

// Taken from https://en.cppreference.com/w/cpp/experimental/constraints
template <typename T>
concept bool Integral = std::is_integral<T>::value;

template <Integral T> // Using concept Integral.
void foo(T i) { /* ... */ } // 'i' has to be integral, or compile time error.

现在,区间(简化)的概念是符合以下接口(伪代码)的:

Range {
    begin()
    end()
}

使用这个工具,我们可以编写以下代码:

template <Range T>
std::ostream& operator<<(std::ostream& out, T&& rng) {
    std::copy(std::forward<T>(rng), std::make_ostream_joiner(out, ", "));
    return out;
}

它适用于任何具有 begin()end() 函数的内容,例如:

std::cout << std::vector{1, 2, 3, 4, 5} << std::endl; // Ok
std::cout << std::list{1, 2, 3, 4, 5} << std::endl; // Ok

此外,请注意我使用了std::make_ostream_joiner而不是std::ostream_iterator。它使用了一种新的C++20迭代器std::ostream_joiner,在每两个对象之间写入分隔符并跳过额外的尾随分隔符,即您将获得"1, 2, 3, 4, 5"而不是"1, 2, 3, 4, 5, "
希望所有这些特性都能进入C++20 :)
注:所有给出的示例都是假想的C++20代码,目前没有任何已知的发布编译器可以编译。

概念已经在C++20中了 :) 另外,您的约束operator<<的语法是错误的。 - Rakete1111
@Rakete1111 是的,在我看来这很不错,但它是铁板钉钉的吗? - Felix Glas
好的,一切都没有定局。如果我没记错,这曾经发生在C++0x概念上,但我不知道是否还有其他特性在被纳入标准后被删除的情况。因此,这种情况是非常不可能的。 - Rakete1111
@Rakete1111,你能否具体说明一下我的语法有什么问题? - Felix Glas
Range&& rng这里的Range概念是无效的。你需要使用template<Range T> std::ostream& operator<<(std::ostream&, T&& rng);,因为你的约束类型是T,而不是Range - Rakete1111
@Rakete1111,你说得没错,感谢指出我的错误。 - Felix Glas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接