9

I'm trying to do it this way:

template <typename T>
ostream &operator<<(ostream &os, T &arr)
{ /*...*/ }

But can T represent an array? Is it correct to overload the << operator for an array?


EDIT:

According to Kerrek SB's advice, here is my implementation for <<:

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;
}

Is my implementation right? I got a compilation error.

Mateen Ulhaq
  • 24,552
  • 19
  • 101
  • 135
Alcott
  • 17,905
  • 32
  • 116
  • 173

2 Answers2

10

You could do this:

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

This works only for compile-time arrays, of course. Note that you are not allowed to instantiate this template when T is a built-in type or a type in the std namespace!

Probably best to make this inline if possible, since you'll cause a separate instantiation for every N. (The pretty printer has an example of this.)

You will notice, though, that the blanket template introduces an ambiguity, because os << "Hello" now has two possible overloads: the template matching const char (&)[6], and the (non-template) overload for the decay-to-pointer const char *, which both have identical conversion sequences. We can resolve this by disabling our overload for char arrays:

#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;
}

In fact, to be even more general you can also make the basic_ostream parameters template parameters:

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;
}

In view of the fact that T must be a user-defined type, you could even replace is_same<T, char> with is_fundamental<T> to get a bit more checking (but users still must not use this for arrays of standard library types).

Community
  • 1
  • 1
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • thanks, but I don't understand why it cause a separate instantiation for every N if not implemented inline? – Alcott Sep 16 '11 at 01:34
  • Well, it's a template, so every template instance may end up as a separate function in your binary. If you inline, you can possibly avoid the function call entirely, although this is ultimately up to the compiler. – Kerrek SB Sep 16 '11 at 01:38
  • got it. With this operator<< having 2 template args, how could I specify the second arg N? Apparently I cannot just simply use "cout << ar;", can I? – Alcott Sep 16 '11 at 01:42
  • Try it :-) This is a template *function*, so template parameter deduction applies and you don't have to specify anything: `int a[10]; std::cout << a;`. – Kerrek SB Sep 16 '11 at 01:43
  • well, I got compile-error, I will edit my post to add my implementation for <<. – Alcott Sep 16 '11 at 01:47
  • Oh, of course - you shoot yourself in the foot, because you're now making an ambgious overload for `const char[]`! Probably better to make the template a bit more specific, or use something more advanced like the pretty printer does. – Kerrek SB Sep 16 '11 at 01:52
  • Make the template more specific? Sorry, I don't think I understand what you mean. Why my impl is ambiguous? – Alcott Sep 16 '11 at 01:58
  • @Alcott: Well, say you have your own template `Foo`. Then you could make the overload only for arrays of those guys: `template ... (..., const Foo (&arr)[N])`. Then there wouldn't be a conflict with string literals. – Kerrek SB Sep 16 '11 at 01:59
  • yes, you're right, I got through the compilation now. One more question, if I don't add another template overload (as you did in your UPDATE), can I do something else to get around? – Alcott Sep 16 '11 at 02:12
  • @Alcott: If you never ever print string literals (while the template is visible), you should be fine :-) Otherwise, no, because string literals are arrays, and the ostream operator uses char pointers, so it depends on the decay. – Kerrek SB Sep 16 '11 at 02:16
  • I still don't quite understand, why if I `inline` the template function, then I can "avoid the function call entirely"? Does it mean different `N` will not make the compiler instantiate a different function? – Alcott Aug 18 '12 at 13:40
  • @Alcott: Correct. For different `N`, you would just get inlined code, which would actually look the same for all `N` because of array-to-pointer decay. – Kerrek SB Aug 18 '12 at 14:05
  • So, there won't be so many different functions generated for different N`, but this will make the binary smaller or what? – Alcott Aug 19 '12 at 03:58
  • @Alcott: Smaller than what? If the compiler does any optimization at all (and if you don't take function pointers), then there shouldn't be any actual functions. It'll be just like if you had put the code in place. That may or may not affect the binary size, depending on how much code your printing loop actually requires. – Kerrek SB Aug 19 '12 at 08:09
  • @Alcott: Also, my earlier comment wasn't quite correct: The functions are of course different for different `N`, so the resulting code would also look different for each `N`. But in that light, inlining may actually be desirable in any case, because if you have lots of different values of `N`, then you'd need to generate lots of function code anyway, so there's really no downside to inlining at all. – Kerrek SB Aug 19 '12 at 08:11
  • [This answer is wrong](http://stackoverflow.com/questions/31135228/overloading-output-operator-for-arrays) – Anmol Singh Jaggi Jun 30 '15 at 12:51
  • This does not work with `spdlog` which consumes these stream operators. A quick and dirty workaround is to explicitly make use of a stringstream. – Steven Lu May 17 '21 at 23:30
3

Another way you could do this would be something like the following:

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

Where T will take a pointer to an array (i.e., it will be the pointer-type that the array will decay into), and the int portion of the pair would be the size of the array. You could then use it like the following:

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

cout << make_pair(array, 10);

One plus with this method is it will also work for dynamic arrays (i.e., arrays you allocate on the heap, etc.)

Jason
  • 31,834
  • 7
  • 59
  • 78
  • I have a very faint feeling that you may get in trouble with ADL here if your `T` isn't a user-defined type, though I can't be sure. – Kerrek SB Sep 16 '11 at 01:45
  • I tested it with a build-in type like `int` ... seemed to work just fine ... I can't think why this would conflict with ADL rules. If you have a `std::pair` object, the template should be capable of deducing the type `T`, and rejecting any instantiations using a `std::pair` where `U` is not of type `int`. – Jason Sep 16 '11 at 01:48