3

I'm using a tuple as a key to track elements in a map, and then later iterating over the map to produce a string version of the map. To help me with the conversion, I have a template convenience function that will concatenate the tuple used as the key (inspired by this answer).

#include <iostream>
#include <string>
#include <sstream>
#include <tuple>

template<std::size_t idx = 0, typename ...T>
inline typename std::enable_if<idx == sizeof...(T), void>::type
cat_tuple(std::tuple<T...> &t, std::stringstream &s){
}

template<std::size_t idx = 0, typename ...T>
inline typename std::enable_if<idx < sizeof...(T), void>::type
cat_tuple(std::tuple<T...> &t, std::stringstream &s){
    if (idx != 0)
        s << ",";
    s << std::get<idx>(t);
    cat_tuple<idx+1, T...>(t, s);
}

typedef std::tuple<int, int> my_tuple;
int main(){
    my_tuple t(1, 2);
    std::stringstream s;
    cat_tuple(t, s);
    std::cout << s.str() << std::endl; //Correctly prints "1,2"
}

I can insert elements into the map and iterate without error

#include <iostream>
#include <string>
#include <sstream>
#include <tuple>
#include <map>

typedef std::tuple<int, int> my_tuple;
typedef std::map<my_tuple, int> my_map;
int main(){
    my_map m;
    my_tuple t(1, 2);
    m.insert(std::pair<my_tuple, int>(t, 1));
    std::stringstream s;
    for(my_map::iterator i = m.begin(); i != m.end(); ++i)
        s << i->second;
    std::cout << s.str() << std::endl; //Correctly prints "1"
}

But when I try to iterate over the map, I get a substitution error:

#include <iostream>
#include <string>
#include <sstream>
#include <tuple>
#include <map>

template<std::size_t idx = 0, typename ...T>
inline typename std::enable_if<idx == sizeof...(T), void>::type
cat_tuple(std::tuple<T...> &t, std::stringstream &s){
}

template<std::size_t idx = 0, typename ...T>
inline typename std::enable_if<idx < sizeof...(T), void>::type
cat_tuple(std::tuple<T...> &t, std::stringstream &s){
    if (idx != 0)
        s << ",";
    s << std::get<idx>(t);
    cat_tuple<idx+1, T...>(t, s);
}

typedef std::tuple<int, int> my_tuple;
typedef std::map<my_tuple, int> my_map;
int main(){
    my_map m;
    my_tuple t(1, 2);
    m.insert(std::pair<my_tuple, int>(t, 1));
    std::stringstream s;
    for(my_map::iterator i = m.begin(); i != m.end(); ++i){
        if (i != m.begin())
            s << "\n";
        cat_tuple(i->first, s); //Substitution error, see below
        s << " " << i->second;
    }
    std::cout << s.str() << std::endl;
}

producing (g++ 4.2.1 on OSX, removed extraneous enable_if notes)

$ g++ asdf.cc -std=c++11
asdf.cc:31:3: error: no matching function for call to 'cat_tuple'
                cat_tuple(i->first, s); //Substitution error, see below
                ^~~~~~~~~
...
asdf.cc:14:1: note: candidate template ignored: substitution failure [with idx =
      0, T = <int, int>]
cat_tuple(std::tuple<T...> &t, std::stringstream &s){
^
1 error generated.

As it says in the error message, the template I want it to use was ignored due to substitution failure. What difference did I introduce when I moved it to the map, and how would I correct it?

Community
  • 1
  • 1
sabreitweiser
  • 643
  • 3
  • 13

1 Answers1

4

Change both your cat_tuple function templates to

cat_tuple(std::tuple<T...> const& t, std::stringstream &s)
//                         ^^^^^

This is necessary because an std::map's keys are const.

Live demo

In any case, cat_tuple's tuple parameter should be const& since it doesn't modify the argument.


You may want to rename cat_tuple to tuple_printer or something similar and change the std::stringstream parameter to std::ostream, that way you can pass it std::cout if desired to output to stdout.

Also, the current name is too reminiscent of std::tuple_cat.

Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • Worked like a charm, thanks! I feel obtuse for missing it. I'm actually putting it into a string rather than cout, but using a std::ostringstream seems to be a good idea. Thanks for the tip! – sabreitweiser Apr 27 '15 at 23:32
  • @sabreitweiser Both `std::stringstream` and `std::ostringstream` derive from `std::ostream`, so the example I linked to will compile as is even after you make the replacement I suggested. – Praetorian Apr 27 '15 at 23:36