如何在C++中为数组重载运算符<<?

9
我正在尝试这样做:
template <typename T>
ostream &operator<<(ostream &os, T &arr)
{ /*...*/ }

但是,T能够代表一个数组吗?重载<<运算符来操作数组是正确的吗?


编辑:

根据Kerrek SB的建议,这是我的<<实现:

template <typename T, unsigned int N>
ostream &operator<<(ostream &os, const T (&arr)[N])
{
    int i;
    for(i = 0; i < N; i++)
        os << arr[i] << " ";
    os << endl;
    return os;
}

我的实现是否正确?我遇到了编译错误。

2个回答

10
你可以这样做:
template <typename T, unsigned int N>
std::ostream & operator<<(std::ostream & os, const T (&arr)[N])
{
  // ..
  return os;
}

当然,这仅适用于编译时数组。请注意,您不允许在T为内置类型或std命名空间中的类型时实例化此模板!

如果可能的话,最好将此函数设为内联函数,因为每个N都会引起一个单独的实例化。(漂亮的打印机有一个示例。)

但是您会注意到,通用模板引入了歧义,因为os << "Hello"现在有两个可能的重载:匹配const char (&)[6]的模板和(非模板)衰减为指针const char *的重载,它们都具有相同的转换序列。我们可以通过禁用char数组的重载来解决这个问题:

#include <ostream>
#include <type_traits>

template <typename T, unsigned int N>
typename std::enable_if<!std::is_same<T, char>::value, std::ostream &>::type
operator<<(std::ostream & os, const T (&arr)[N])
{
  // ..
  return os;
}

事实上,为了更加通用,您还可以将basic_ostream参数作为模板参数:
template <typename T, unsigned int N, typename CTy, typename CTr>
typename std::enable_if<!std::is_same<T, char>::value,
                        std::basic_ostream<CTy, CTr> &>::type
operator<<(std::basic_ostream<CTy, CTr> & os, const T (&arr)[N])
{
  // ..
  return os;
}

鉴于 T 必须是用户定义的类型,你甚至可以将 is_same<T, char> 替换为 is_fundamental<T> 以进行更多检查(但用户仍不得将其用于标准库类型的数组)。

明白了。使用这个 operator<< 有两个模板参数,我该如何指定第二个参数 N?显然我不能简单地使用 "cout << ar;",对吗? - Alcott
让模板更具体化?抱歉,我不太明白你的意思。为什么我的实现是含糊不清的? - Alcott
@Alcott:假设你有自己的模板 Foo<T>。那么你可以只为这些数组重载:template <typename T, int N> ... (..., const Foo<T> (&arr)[N])。这样就不会与字符串字面值发生冲突了。 - Kerrek SB
这个答案是错误的:https://dev59.com/nl0Z5IYBdhLWcg3wvCSd - Anmol Singh Jaggi
这种做法不能与spdlog一起使用,因为它消耗了这些流操作符。一个快速而不太优雅的解决方法是明确地使用stringstream。 - Steven Lu
显示剩余12条评论

3
你可以用以下方式之一来实现此操作:

另一种方法是采用以下方式:

template<typename T>
ostream& operator<<(ostream &out, const std::pair<T, int>& array)
{
    //...code
    return out;
}

这里的 T 将会接收一个数组指针(也就是说,它将是数组衰减成的指针类型),而这个二元组中的 int 将会是数组的大小。你可以像下面这样使用它:

int array[10];
//...some code that initializes array, etc.

cout << make_pair(array, 10);

这种方法的一个优点是它也适用于动态数组(即在堆上分配的数组等)


我有一种非常微弱的感觉,如果你的“T”不是用户定义的类型,那么在这里使用ADL可能会让你陷入麻烦,尽管我不能确定。 - Kerrek SB
我使用内置类型,如 int 进行了测试...似乎工作得很好...我想不出这会与 ADL 规则冲突的原因。如果你有一个 std::pair<T, U> 对象,那么模板应该能够推断出类型 T,并拒绝任何使用 U 不是 int 类型的 std::pair<T, U> 实例化。 - Jason

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