Timothy Shields
Timothy Shields

Reputation: 79461

Get Iterator Type from Range Type

Related: Template Specialization for each Range Type

Background

In C++11 the range-based for loop handles three kinds of "ranges," outlined here (link). I've quoted the relevant part below.

Syntax

for (range_declaration : range_expression) loop_statement

Explanation

The above syntax produces code similar to the following (__range, __begin and __end are for exposition only):

{
     auto && __range = range_expression;
     for (auto __begin = begin_expr,
         __end = end_expr;
         __begin != __end; ++__begin) {
         range_declaration = *__begin;
         loop_statement
     }
}

The range_expression is evaluated to determine the sequence or range will be iterated over. Each element of the sequence is dereferenced, and assigned to the variable using the type and name given in the range_declaration.

The begin_expr and end_expr are defined to be either:

  • If (__range) is an array, then (__range) and (__range + __bound), where __bound is the array bound;
  • If (__range) is a class and has either a begin or end member (or both), then begin_expr is __range.begin() and end_expr is __range.end();
  • Otherwise, begin(__range) and end(__range), which are found based on argument-dependent lookup rules with std as an associated namespace.

Question

How can I write something to get the type of the iterator the range-based for loop uses, given the type of range_expression? (Similar to how std::iterator_traits<Iterator>::value_type gets me the type of iterator values given the type of Iterator.)

I want to be able to write range_traits<Range>::iterator_type given some range type Range and have that be the type of the iterator that a range-based for loop would use.

I tried this:

template <typename Range>
struct range_traits
{
    //Try to use ADL to get correct "begin" function, or std::begin by default
    using std::begin;
    typedef typename decltype(begin(std::declval<Range>())) iterator_type;
};

This doesn't work, however, because in the context of a using declaration in a class body, a using declaration is for declaring base class members.

The following does work, because the using std::begin does what I want it to (makes std::begin the default if ADL fails).

template <typename Range>
void example(Range& range)
{
    using std::begin;
    auto it = begin(range); //it is of the type I want
    //the expression decltype(begin(range)) would get the type I want
}

The problem is that I can't actually get the type "out of" the function body.

Any ideas as to how to accomplish this task? Is there some SFINAE magic that can be used? If it's not clear what I'm trying to accomplish, I'll try to make it clearer.

Upvotes: 1

Views: 1353

Answers (1)

Massa
Massa

Reputation: 8972

This seem to have worked ok:

#include <iterator>

namespace range_trait_namespace {
  using std::begin;
  template<typename Range>struct range_traits {
    typedef decltype(begin(std::declval<Range>())) iterator_type;
  };
}


#include <iostream>
#include <vector>

int main(int, char**) {
  std::cout << "oi\n";
  std::vector<int> i { 1, 2, 3, };
  for( auto x: i )
    std::cout << x << "\n";
  for( range_trait_namespace::range_traits<std::vector<int>>::iterator_type aa = i.begin(); aa != i.end(); ++aa)
    std::cout << *aa << "\n";
  return 0;
}

Upvotes: 2

Related Questions