8

I want specialize a function template for vector and map like containers. For vector I can do like below but I don't know how can I have a specialized version of the function that will be used only for map like containers.

#include <iostream>
#include <vector>
#include <map>

using namespace std;

template<typename Iterator>
void print(Iterator begin, Iterator end)
{
    while (begin != end)
    {
        cout << *begin << endl; // compiler error for map like containers
        ++begin;
    }
}

int main()
{
    vector<int> noVec = { 1, 2, 3 };

    print(noVec.begin(), noVec.end());

    map<int, int> nosMap;
    nosMap[0] = 1;
    nosMap[1] = 2;
    nosMap[3] = 3;

    print(nosMap.begin(), nosMap.end());

    return 0;
}

This question is similar but it suggests to use pair in vector which I don't want to do. I know the specialization can be done with SFINAE but don't know what condition to check for. It would be great if I can achieve this with C++ 11 type_traits.

Community
  • 1
  • 1
Sivachandran
  • 787
  • 7
  • 21

2 Answers2

9

The value_type of a map is some pair so you could check if the value_type of the iterator is a std::pair or not, e.g.

#include <vector>
#include <map>
#include <iostream>

template <typename> 
struct is_pair : std::false_type 
{ };

template <typename T, typename U>
struct is_pair<std::pair<T, U>> : std::true_type 
{ };



template <typename Iter>
typename std::enable_if<is_pair<typename std::iterator_traits<Iter>::value_type>::value>::type
print(Iter begin, Iter end)
{
  std::cout << "called with map-like" << std::endl;
  for (; begin != end; ++begin)
  {
    std::cout << begin->second;
  }
  std::cout << std::endl;
}

template <typename Iter>
typename std::enable_if<!is_pair<typename std::iterator_traits<Iter>::value_type>::value>::type
print(Iter begin, Iter end)
{
  std::cout << "called with vector-like" << std::endl;
  for (; begin != end; ++begin)
  {
    std::cout << *begin;
  }
  std::cout << std::endl;
}



int main()
{
  std::vector<int> vec { 1, 2, 3 };
  std::map<int, int> map {{0, 0}, {1, 1}, {2, 4}, {3, 9}};

  print(vec.begin(), vec.end());
  print(map.begin(), map.end());
}

which prints

called with vector-like
123
called with map-like
0149
Marius Bancila
  • 16,053
  • 9
  • 49
  • 91
MadScientist
  • 3,390
  • 15
  • 19
  • 2
    What I don't understand is how "typename std::iterator_traits::value_type" is converted two template parameters for is_pair (true_type). Is there any link that helps me to understand? – Sivachandran Aug 13 '14 at 07:21
  • 1
    `is_pair` is a struct that has exactly one type as template parameter. At first, the "default" value for any type is defined as `std::false_type`, then `is_pair` is partially specialized for any type that matches the type `std::pair` with the value std::true_type. Even though the partial specialization has *two* template parameters (`T` and `U`), `is_pair` has only a *single* template parameter which ist `std::pair`. – MadScientist Aug 13 '14 at 09:10
5

You don't need to specialize anything. All you have to do is to provide an overloaded output operator<< for std::pair, like the example below:

template<typename T1, typename T2>
std::ostream& operator<<(std::ostream &out, std::pair<T1, T2> const &mp) {
  return (out << "(" << mp.first << ", " << mp.second << ")");
}

LIVE DEMO


edit:

The above solution however as @Benjamin Lindley suggested in the comments might conflict with other template overloads of the output operator<< for std::pair.

If this is the case, alternatively you could write in their own namespace (e.g., namespace detail) two template function overloads (e.g., print_elem), like the example below:

namespace detail {
  template<typename T1, typename T2>
  std::ostream& print_elem(std::ostream &out, std::pair<T1, T2> const &mp) {
    return (out << "(" << mp.first << ", " << mp.second << ")");
  }

  template<typename T>
  std::ostream& print_elem(std::ostream &out, T const &elem) {
      return (out << elem);
  }
}

and change your template print like the example below:

template<typename Iterator>
void print(Iterator begin, Iterator end)
{
    while (begin != end) {
        detail::print_elem(cout, *begin) << endl;
        ++begin;
    }
}

LIVE DEMO

Community
  • 1
  • 1
101010
  • 41,839
  • 11
  • 94
  • 168
  • 1
    It's not unlikely that many people have had the idea of overloading `operator<<` for `std::pair`, and your function will conflict with theirs. I would recommend instead not using `cout <<` directly, but creating a separate pair of overloaded functions in your own namespace. +1 anyway because specializing as narrowly as possible is a better idea than specializing the whole function, most of which is identical for both cases. – Benjamin Lindley Aug 13 '14 at 06:40
  • Overloading `operator<<` works for the particular solution of printing the values to the console. What if the template function is supposed to do something else? Fetch data from a service, store in a database, etc? This solution won't work. – Marius Bancila Aug 13 '14 at 07:20
  • I like the way we can overload operator<< to print the pair. But as others pointed it out, my intention is mostly to have two different function implementation based on the container iterator I am passing. – Sivachandran Aug 13 '14 at 07:24
  • @MariusBancila: So, write overloaded functions which do the thing you want to do, and call those instead. – Benjamin Lindley Aug 13 '14 at 07:25
  • @MariusBancila IMHO this comment is away too far not related to what the OP has asked. I tried to give the simplest solution to precisely what the OP asked, adding "what if"s could end up filling a lot of pages. – 101010 Aug 13 '14 at 07:29
  • 1
    @SivaChandran Your choice is well respected. However, I'm more with the "occam's razor" philosophy (i.e., use the simplest). IMHO making a pair of overloads is far simpler than using advanced C++ template features like SFINAE. – 101010 Aug 13 '14 at 07:34