-1

I am trying to figure out why the following piece of code, where a template of stream output operator function is written, requires using template template parameters:

https://wandbox.org/permlink/W85pV5GhVzI95b3e

#include <iostream>
#include <vector>
#include <deque>
#include <list>

template<template <class> class C, class T>
std::ostream& operator <<(std::ostream& os, const C<T>& objs)
{
    for (auto const& obj : objs)
        os << obj << ' ';
    return os;
}

int main()
{
    std::vector<float> vf { 1.1, 2.2, 3.3, 4.4 };
    std::cout << vf << '\n';

    std::list<char> lc { 'a', 'b', 'c', 'd' };
    std::cout << lc << '\n';

    std::deque<int> di { 1, 2, 3, 4 };
    std::cout << di << '\n';

    return 0;
}

Why can't I just write the template of the operator function like this:

template <class T>
std::ostream& operator <<(std::ostream& os, T& objs) {...}

In this case I get a lot of errors saying: error: ambiguous overload for 'operator<<' (operand types are 'std::ostream' {aka 'std::basic_ostream'} and 'const char') os << obj << ' ';

Can someone help me understand this?

YotKay
  • 1,127
  • 1
  • 9
  • 25
  • Read the error message carefully. The type it's complaining about is not for any of the containers. – StoryTeller - Unslander Monica Mar 29 '18 at 09:54
  • Suppose you use only vector, your function will have this signature std::ostream& operator <<(std::ostream& os, const vector& objs). So at least your template must have 2 arguments: the **vector** (for template class C), and the type **float** (for class T) – Ratah Mar 29 '18 at 10:00
  • But in this case I cannot use this function with an object of a non-template class in this case. That is not what I want. I would like my function to work with anything that is iterable, i.e. to accept objects of both template classes and non-template classes. – YotKay Mar 29 '18 at 10:06
  • @YotKay - And so we reached the core of you [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). Shame you didn't ask about *that interesting problem* instead of this obviously answerable one. – StoryTeller - Unslander Monica Mar 29 '18 at 10:14
  • You are right, I should have asked this question differently. But fortunately we managed to solve this problem here and everything is now completely clear. Thanks everyone. – YotKay Mar 29 '18 at 11:19

3 Answers3

3

While I concur with Jarod42 that you don't need the template template parameter, allow me to demonstrate a simpler solution for your specific case:

template<class T>
auto operator <<(std::ostream& os, T const& objs)
  -> decltype(std::begin(objs), std::end(objs), (os))
{
    for (auto const& obj : objs)
        os << obj << ' ';
    return os;
}

A trailing return type for some expression SFINAE, and everything works as you want it to. The SFINAE part happens in the decltype() return type. The comma operator makes it so std::begin(objs), std::end(objs) and then (os) are checked as being well formed. If either of those is ill-formed, the function isn't considered for overload resolution. But because the type of the comma operator is the same type as its last operand, we get std::ostream& out of (os) due to decltype deduction rules.

Why std::begin and std::end? It just so happens that they are well-formed for pretty much all the types you can feed to a range based for loop. So the check is going to cover any type that adheres to the iteration protocol of a range based for loop.

Here it is, live.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
2

template template argument is not required.

Author of the code assumes that iterable object would be of that form C<T>, which is wrong:

  • There exists C<T> that are not iterable, as std::unique_ptr<T>...
  • std::vector has more arguments (defaulted) and so before C++2a, it won't matches C<T>.

using traits (like in that question) would be better:

template<class T, std::enable_if_t<is_iterable<T>::value>* = nullptr>
std::ostream& operator <<(std::ostream& os, const T& objs)
{
    for (auto const& obj : objs)
        os << obj << ' ';
    return os;
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Thanks, it worked. But I wonder why the error occurs in my case when I don't instantiate the template with anything non-iterable. – YotKay Mar 29 '18 at 10:26
  • @YotKay You are using `operator<<` for float, int and char inside the operator itself. – super Mar 29 '18 at 10:38
0

You can not write the template like this:

template <class T>
std::ostream& operator <<(std::ostream& os, T& objs) {...}

because that would overload ostream for every(!) object. T can be anything, not just a list.

I think the best way to deal with this is to specify the overload for each type of list that u want. E.g:

template <class T>
std::ostream& operator <<(std::ostream& os, std::vector<T>& objs) {...}

template <class T>
std::ostream& operator <<(std::ostream& os, std::list<T>& objs) {...}

and so on...

informant09
  • 158
  • 15
  • But that's what I want. I want a template which would work for anything, as long as it is iterable. Of course I want compilation to fail if I use this template with something non-iterable, that is obvious. – YotKay Mar 29 '18 at 10:01
  • This is not as easy as you might think. Maybe this helps you to get in the right direction: https://stackoverflow.com/questions/13830158/check-if-a-variable-is-iterable – informant09 Mar 29 '18 at 10:04