Template Specialization for each Range Type

2019-06-08 10:48发布

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 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.

1条回答
小情绪 Triste *
2楼-- · 2019-06-08 11:09

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 and std::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:

template<typename Range>
void f(Range& range)
{
    using std::begin;
    using std::end;

    for(auto it = begin(range), end_it = end(range); it != end_it; ++it)
    {
        T& item = *it;
        /* do something custom with item */
    }
}

If you wonder how std::begin/end work, then just look at their declarations:

template< class C >
auto begin( C& c ) -> decltype(c.begin());

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)

template< class T, size_t N >
T* begin( T (&array)[N] );

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.

查看更多
登录 后发表回答