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

T.C*_*.C. 14 c++ foreach c++11 c++14

考虑基于范围的for循环的begin-exprend-expr(N4140 [stmt.ranged]/p1)的规范.鉴于一系列__range类型_RangeT,

begin-exprend-expr确定如下:

  • 如果_RangeT是一个数组类型,开始-EXPR最终EXPR__range__range + __bound分别,其中__bound在阵列的约束.如果_RangeT是未知大小的数组或不完整类型的数组,则该程序格式不正确;
  • 如果_RangeT是一个类型时,不合格-ID小号beginend被查找类的范围_RangeT由类成员访问查找(3.4.5)那样的话,并且如果任一个(或两者)找到至少一个声明,开始-EXPRend-expr分别是__range.begin()__range.end();
  • 否则,开始-EXPR最终EXPRbegin(__range)end(__range)分别在那里beginend在相关联的命名空间(3.4.2)的查找.[ 注意:不执行普通的非限定查找(3.4.1).- 结束说明 ]

是否可以在普通的C++代码中模拟这种确切的行为?也就是说,我们可以编写一个magic_begin和一个magic_end功能模板

for(auto&& p : range_init) { /* statements */ }
Run Code Online (Sandbox Code Playgroud)

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

总是有完全相同的行为?

非答案包括对std::begin/的合格调用std::end(除了其他事项之外不处理第三个子弹),并且using std::begin; begin(range);因为如果ADL 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;
Run Code Online (Sandbox Code Playgroud)

我希望magic_begin(a)/ magic_begin(b)/ magic_begin(c)/ magic_begin(d)是一个编译错误,并magic_begin(e)返回(int*)nullptr.

Col*_*mbo 12

以下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);}
Run Code Online (Sandbox Code Playgroud)

演示.请注意,GCC查找被破坏,因为它不考虑typename T::beginin的非类型名称TestMemType::test_end/begin.可在此处找到变通方案草图.

第2步中的检查要求类类型是可派生的,这意味着此方法不适用于final类或联合 - 如果它们具有名称为begin/ 的不可访问成员end.