c++ 模拟基于范围的for循环的开始/结束行为

考虑基于范围的for循环的begin-expr和end-expr(N4140 [stmt.ranged] / p1)的规范.给定__range类型_RangeT的范围,

begin-expr and end-expr are determined as follows:

  • if _RangeT is an array type, begin-expr and end-expr are __range and __range + __bound, respectively, where __bound is
    the array bound. If _RangeT is an array of unknown size or an array
    of incomplete type, the program is ill-formed;
  • if _RangeT is a class type, the unqualified-ids begin and end are looked up in the scope of class _RangeT as if by class member access
    lookup (3.4.5), and if either (or both) finds at least one
    declaration, begin-expr and end-expr are __range.begin() and
    __range.end(), respectively;
  • otherwise, begin-expr and end-expr are begin(__range) and end(__range), respectively, where begin and end are looked up in
    the associated namespaces (3.4.2). [ Note: Ordinary unqualified
    lookup (3.4.1) is not performed. —end note ]

是否可以在普通的C代码中模拟这个确切的行为?即我们可以编写一个magic_begin和magic_end函数模板

for(auto&& p : range_init) { /* statements */ }

{
    auto&& my_range = range_init;
    for(auto b = magic_begin(my_range), e = magic_end(my_range); b != e; ++b){
        auto&& p = *b;
        /* statements */
    }
}

总是有完全相同的行为?

非答案包括对std :: begin / std :: end的限定调用(不处理第三个项目符号等),并使用std :: begin;开始(范围);因为除了别的以外,如果ADL for begin发现一个同样好的std :: begin的重载,那就是含糊不清的.

为了说明,给出

namespace foo {
    struct A { int begin; }; 
    struct B { using end = int; };
    class C { int* begin(); int *end(); }; // inaccessible
    struct D { int* begin(int); int* end();};
    struct E {};

    template<class T> int* begin(T&) { return nullptr; }
    template<class T> int* end(T&) { return nullptr; }
}

foo::A a; foo::B b; foo::C c; foo::D d; foo::E e;

我想要magic_begin(a)/ magic_begin(b)/ magic_begin(c)/ magic_begin(d)成为一个编译错误,而magic_begin(e)返回(int *)nullptr.

以下对SFINAE友好的方法似乎可以正常工作(有关例外,请参见下文):

#include <type_traits>

namespace detail {
    struct empty {};
    template <typename T>
    using base = std::conditional_t<std::is_class<T>{} && not std::is_final<T>{},
                                    T, empty>;

    struct P1 {typedef int begin, end;};
    template <typename U>
    struct TestMemType : base<U>, P1 {
        template <typename T=TestMemType, typename=typename T::begin>
        static std::true_type test_begin(int);
        template <typename T=TestMemType, typename=typename T::end>
        static std::true_type test_end(int);

        static std::false_type test_begin(float), test_end(float);
    };

    template <typename T>
    constexpr bool hasMember = !decltype(TestMemType<T>::test_begin(0)){}
                            || !decltype(TestMemType<T>::test_end(0)){};

    //! Step 1
    template <typename T, std::size_t N>
    constexpr auto begin(int, T(&a)[N]) {return a;}
    template <typename T, std::size_t N>
    constexpr auto end(int, T(&a)[N]) {return a+N;}

    //! Step 2 - this overload is less specialized than the above.
    template <typename T>
    constexpr auto begin(int, T& a) -> decltype(a.begin()) {return a.begin();}
    template <typename T>
    constexpr auto end(int, T& a) -> decltype(a.end()) {return a.end();}

    //! Step 3
    namespace nested_detail {
        void begin(), end();
        template <typename T>
        constexpr auto begin_(T& a) -> decltype(begin(a)) {return begin(a);}
        template <typename T>
        constexpr auto end_(T& a) -> decltype(end(a)) {return end(a);}
    }
    template <typename T, typename=std::enable_if_t<not hasMember<std::decay_t<T>>>>
    constexpr auto begin(float, T& a) -> decltype(nested_detail::begin_(a))
    {return nested_detail::begin_(a);}
    template <typename T, typename=std::enable_if_t<not hasMember<std::decay_t<T>>>>
    constexpr auto end(float, T& a) -> decltype(nested_detail::end_(a))
    {return nested_detail::end_(a);}
}

template <typename T>
constexpr auto magic_begin(T& a) -> decltype(detail::begin(0, a))
{return detail::begin(0, a);}
template <typename T>
constexpr auto magic_end  (T& a) -> decltype(detail::end  (0, a))
{return detail::  end(0, a);}

Demo.请注意,GCC查找是坏的,因为它不考虑类型名称T :: begin在TestMemType :: test_end / begin中的非类型名称.可以找到解决方法草图here.

步骤2中的检查要求可以派生类类型,这意味着该方法不能适用于最终类或联合 – 如果这些类具有名称为begin / end的无法访问的成员.

翻译自:https://stackoverflow.com/questions/32293860/simulating-the-range-based-for-loops-begin-end-behavior

转载注明原文:c++ 模拟基于范围的for循环的开始/结束行为