2

I am trying to create a template for easily outputting containers, for the purpose of debugging code. I would like to be able to do

int a = 3;
pair<int,int> b = {2,3};
vector<int> c = {1,2,3,4};
map<int,int> m;
m[2] = 3;
m[3] = 4;
dbg(a,b,c,m);

OUTPUT:
[a,b,c,m] = [ 3 , (2,3) , {1,2,3,4} , {(2,3),(3,4)} ]

So far I have this:

#define dbg(x...) cout << "[" << #x << "] = [ "; _dbg(x); cout << "]"

template <typename T> void _dbg(T t)
{
    cout << t << " ";
}
template<typename T, typename... Args>
void _dbg(T t, Args... args) // recursive variadic function
{
    cout << t << " , ";
    _dbg(args...);
}

template <typename T, typename V>
ostream& operator<<(ostream& os, const pair<T, V> p)
{
    cout << "(" << p.first << "," << p.second << ")";
}

template <typename T>
ostream& operator<<(ostream& os, const vector<T>& dt)
{
    cout << "{";
    auto preEnd = dt.end();
    preEnd--;
    for (auto bgn = dt.begin(); bgn != preEnd; bgn++)
        cout << *bgn << ",";
    cout << *preEnd << "}";
    return os;
}

template <typename T>
ostream& operator<<(ostream& os, const set<T>& dt)
{
    cout << "{";
    auto preEnd = dt.end();
    preEnd--;
    for (auto bgn = dt.begin(); bgn != preEnd; bgn++)
        cout << *bgn << ",";
    cout << *preEnd << "}";
    return os;
}

template <typename T, typename V>
ostream& operator<<(ostream& os, const map<T, V>& dt)
{
    cout << "{";
    auto preEnd = dt.end();
    preEnd--;
    for (auto bgn = dt.begin(); bgn != preEnd; bgn++)
        cout << *bgn << ",";
    cout << *preEnd << "}";
    return os;
}

And it works great! It's just that I don't want to define a function for every single container type, when they all would have the same body (talking about the last 3 functions). I tried something like

template<typename C, typename T>
ostream& operator<<(ostream& os, const C<T>& dt)

But I got

error: C is not a template

and I tried

template<typename C>
ostream& operator<< (ostream& os, const C& dt)

And got

error: no match for 'operator<<' (operand types are 
'std::ostream {aka std::basic_ostream<char>}' and 'std::set<int>')|

So how do I just have one generic function that deals with any container (ex. one being template<typename T> vector<T>, and the other set<T>??)

JeJo
  • 30,635
  • 6
  • 49
  • 88
Jeff
  • 97
  • 4
  • 4
    Use iterators (in a helper function) instead of containers and pass the container to a single function which accepting a template template argument, and call that helper function! – Const Aug 27 '21 at 13:44
  • Could you provide some example code to illustrate a function accepting a template template argument, please? I've never heard of that before. – Jeff Aug 27 '21 at 13:53
  • Does this answer your question? [Template class with template container](https://stackoverflow.com/questions/16596422/template-class-with-template-container) – JaMiT Aug 27 '21 at 13:55

2 Answers2

2

How do I just have one generic function that deals with any container?

As @Const already mentioned in the comment section, you can provide a templated operator<< overload which has template template parameter, so that it can accept the containers of your choice, whose ranges will be passed to a helper function (i.e. print_helper in following example) to print the elements. This required the elements of the container to have operator<< defined; like you have provided the overload for std::pair type.

template <typename Iterator>
std::ostream& print_helper(std::ostream& os
    , Iterator start, const Iterator end) noexcept
{
    for (; start != end; ++start) os << *start;
    return os << '\n';
}

// template template parameter
template <template<typename Type, typename... Rest> class Conatiner, typename Type, typename... Rest> 
std::ostream& operator<<(std::ostream& os
    , const Conatiner<Type, Rest...>& container) noexcept
{
    // pass the ranges to helper function
    return print_helper(os, std::begin(container), std::end(container)); 
}

This works for and here is (the complete example code)

JeJo
  • 30,635
  • 6
  • 49
  • 88
1

If you have C++ 20, this is made easier by having a template constrained on a concept.

N.b. std::string and char[] are both types that satisfy std::ranges::range, but we don't want those to use our template

template <typename T>
concept non_string_range = std::ranges::range<T> && !std::is_same_v<std::ranges::range_value_t<T>, char>;

template <non_string_range T>
std::ostream& operator<<(std::ostream& os, const T & range) {
    std::cout << "{";
    std::string prefix;
    for (auto & elem : range) {
        std::cout << prefix << elem;
        prefix = ",";
    }
    std::cout << "}";
    return os;
}

Prior to C++20 you can do similar things with a trait and std::enable_if

Caleth
  • 52,200
  • 2
  • 44
  • 75