1

I have a function which "zips" multiple containers for range based foor loop iteration. (taken from a another Stack Overflow post)

template<class... Conts>
auto zip_range(Conts&... conts)
  -> decltype(boost::make_iterator_range(boost::make_zip_iterator(boost::make_tuple(conts.begin()...)),
              boost::make_zip_iterator(boost::make_tuple(conts.end()...)))) {
  return {boost::make_zip_iterator(boost::make_tuple(conts.begin()...)),
      boost::make_zip_iterator(boost::make_tuple(conts.end()...))};
}

I can use this function like so:

std::vector<int> x({1,2,3});
std::vector<int> y({4,5,6});
for ( auto const& entries : zip_range(x,y) ) {
  std::cout << entries.get<0>() << " " << entries.get<1>() << std::endl;
}

And get expected behavior:

1 4
2 5
3 6

If I try to define a function which takes two std::initizalizer_list arguments, passes them to a vector, and tries to loop:

template <class T1, class T2>
void foo(const std::initializer_list<T1>& il1,
         const std::initializer_list<T2>& il2) {

  std::vector<T1> v1(il1);
  std::vector<T2> v2(il2);

  for ( auto const& zipitr : zip_range(v1,v2) ) {
    std::cout << zipitr.get<0>() << " " << zipitr.get<1>() << std::endl;
  }
}

I get a compilation error that isn't very helpful:

testing.cpp: In function ‘void foo(const std::initializer_list<_Tp>&, const std::initializer_list<T2>&)’:
testing.cpp:34:32: error: expected primary-expression before ‘)’ token
     std::cout << zipitr.get<0>() << " " << zipitr.get<1>() << std::endl;
                            ^
testing.cpp:34:58: error: expected primary-expression before ‘)’ token
     std::cout << zipitr.get<0>() << " " << zipitr.get<1>() << std::endl;

Why am I unable to do this? (much less go straight to giving the initializer lists to the function zip_range)

Praetorian
  • 106,671
  • 19
  • 240
  • 328
ddavis
  • 337
  • 5
  • 15

1 Answers1

4

The type of zipitr is dependent on T1 and T2 and that type has a nested member function template get. So to tell the compiler get is a dependent function template, you need to add the template keyword immediately before it.

std::cout << zipitr.template get<0>() << " " << zipitr.template get<1>() << std::endl;
//                  ^^^^^^^^                           ^^^^^^^^

Read this answer for details of why this is needed.

An alternative that avoids the template keyword is to use the boost::get free function.

std::cout << boost::get<0>(zipitr) << " " << boost::get<1>(zipitr) << std::endl;

Your zip_range function can be simplified (or replaced) using boost::combine

#include <boost/range/combine.hpp>

template<class... Conts>
auto zip_range(Conts&... conts)
-> decltype( boost::combine(conts...) )
{
  return boost::combine(conts...);
}

Live demo


The reason you can't pass {1,2,3} directly to zip_range is that a braced-init-list is not an expression, so it doesn't have a type, which means template argument deduction cannot work.

auto has a special rule to deal with them that allows it to deduce the type as std::initializer_list<T>, so the following works:

auto l1 = {1,2,3};
auto l2 = {4,5,6};
for ( auto const& zipitr : zip_range(l1,l2) ) {
  std::cout << boost::get<0>(zipitr) << " " << boost::get<1>(zipitr) << std::endl;
}

Live demo

Community
  • 1
  • 1
Praetorian
  • 106,671
  • 19
  • 240
  • 328