Search code examples
c++templatesoverloadingpartial-specialization

Generic algorithm for calling print on each element in the collection


When writing a template function like:

template<class T> void print(T const & collection)

When looping through the collection and dereferencing the iterator everything works right if you have something like vector<int> unless you change it to vector<int*>. What's the best way to deal with the differences in a single template function whilst not duplicating code?


Solution

  • I would write a single template function do_print that delegates to a class template printer. The class template is a function object that does the pretty printing, and that you partially specialize for T* by simply calling the pretty print version on *t.

    So there is no duplication of the pretty printing code and a minor inconvenience for writing two lightweight implementation classes (these get optimized away by any modern compiler, so there is no runtime overhead).

    I prefer this solution over SFINAE tricks because partial class specialization gives you much more control (and much better error messages) than function overloading tricks. It's also recommended by the Alexandrescu & Sutter Coding Standards.

    BTW, this code will also work for T** because the specialization for T* delegates to the code for T. So T** is send to T* and finally to T. In fact, arbitrary levels of indirection get reduced to printing the elements pointed to by pointers.

    #include <iostream>
    #include <vector>
    
    namespace detail {
    
    template<typename T>
    struct printer
    {
       void operator()(T const& t) 
       { 
          std::cout << t; // your pretty print code here
       }  
    };
    
    template<typename T>
    struct printer<T*>
    {
       void operator()(T const* t) 
       { 
          printer<T>()(*t); // delegate to printing elements (no duplication of prettty print)
       }
    };
    
    }
    
    template<typename T>
    void do_print(T const& t)
    {
       detail::printer<T>()(t);
    }
    
    template<typename C>
    void print(C const& collection)
    {
       for(auto&& c: collection) 
          do_print(c);
       std::cout << "\n";
    }
    
    int main()
    {
       int a = 1;
       int b = 2;
    
       auto c = &a;
       auto d = &b;
    
       std::vector<int> v1 { a, b };
       std::vector<int*> v2 { c, d };
       std::vector<int**> v3 { &c, &d };
    
       print(v1);
       print(v2);
       print(v3);
    }
    

    Output on Live Work Space