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 therange_declaration
.The
begin_expr
andend_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), thenbegin_expr
is__range.begin()
andend_expr
is__range.end()
;- Otherwise,
begin(__range)
andend(__range)
, which are found based on argument-dependent lookup rules withstd
as an associated namespace.
Question
How should I write a template function with three specializations that handle the exact same three cases in the quoted bullet list?
I'm thinking something like the following, but I'm not sure how to do this correctly.
//Handle third bullet - default case
template <typename Range>
void f(Range& range)
{
for (auto it = begin(range); it != end(range); ++it)
{
T& item = *it;
/* do something custom with item */
}
}
//Handle first bullet - "range" is an array
template <>
void f<T[]>(T[] range)
{
auto end = range + sizeof(range)/sizeof(*range);
for (auto it = range; it != end; ++it)
{
T& item = *it;
/* do something custom with item */
}
}
//Handle second bullet - "range" with "begin" and/or "end" function
template <>
void f<RangeBE>(RangeBE& range)
{
//Somehow restrict type RangeBE to classes that have
//begin or end member functions...
//Can enable_if be used for this?
for (auto it = range.begin(); it != range.end(); ++it)
{
T& item = *it;
/* do something custom with item */
}
}
Am I approaching this completely incorrectly? Is this even possible to do with C++11 (i.e., does the compiler get to do something special to accomplish this kind of specialization)? Please enlighten me. :)
Some clarification...
My question is how to handle all three possible types of "range" (from the quoted bullet list) as inputs to a function I write. (I will be using the knowledge gained here to actually write a class qualified with template <typename Range>
in the same way, where the class has specializations that handle all three types of ranges.)
My question is not how to write a class that satisfies one of the three possible types of "range" (from the quoted bullet list). There are multiple SO questions about that - this is not a duplicate of those.
My question is not how to use range-based for loops.
"Just use range-based for loops in f
" is not an option or an answer. What I'm actually trying to do is mimic the semantics of range-based for loops, but because of complications outside the scope of this question, I can't simply use a range-based for loop inside of the example function f
. Please accept the fact that I have good reason to not "Just use a range-based for loop." It would be too complicated to explain why.
The easiest way to mimic the semantics of a range-based for loop is to use a range-based for loop. But since your clarification makes that not an option, and we are left wondering what can you possibly intend to do, then there is another approach:
Use
std::begin
andstd::end
which already exhibit the semantics you expect, and are written in plain C++11. Remembering to enable ADL to kick in, to mimic the third bullet in the semantics on a range-based for loop, it ends up looking something like this:If you wonder how
std::begin/end
work, then just look at their declarations:This overload is a function template that has an expression in it to deduce its return type. Due to SFINAE, when said expression results in an error in the immediate context once replaced
C
with the actual type, it is simply discarded from the overload set (as if it didn't even existed)This overload handles the array case. Note that your approach is incorrect, as you are dealing with an array of incomplete size, and there is no way you can get the number of elements in one of those.