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

时间:2015-08-30 06:26:00

标签: c++ c++11 foreach c++14

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

  

begin-expr end-expr 的确定如下:

     
      
  • 如果_RangeT是数组类型,则 begin-expr end-expr 分别为__range__range + __bound__bound的位置   数组绑定。如果_RangeT是未知大小的数组或数组   不完整的类型,该程序是不正确的;
  •   
  • 如果_RangeT是类类型,则会在类begin的范围内查找 unqualified-id s end_RangeT好像通过类成员访问   查找(3.4.5),如果其中任何一个(或两个)找到至少一个   声明, begin-expr end-expr __range.begin()和   分别为__range.end();
  •   
  • 否则, begin-expr end-expr 分别为begin(__range)end(__range),其中begin和{{ 1}}被查找   关联的命名空间(3.4.2)。 [注意:普通不合格   查找(3.4.1)未执行。 - 结束记录]
  •   

是否可以在普通C ++代码中模拟此精确行为?也就是说,我们可以编写endmagic_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,因为除其他事项外,如果不明确的话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; / magic_begin(a) / magic_begin(b) / magic_begin(c)成为编译错误,并magic_begin(d)返回magic_begin(e)

2 个答案:

答案 0 :(得分: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);}

Demo。请注意,GCC查找已被删除,因为它没有考虑typename T::beginTestMemType::test_end/begin的非类型名称。可以找到解决方案草图here

第2步中的检查要求类类型是可派生的,这意味着此方法无法正确使用final类或联合 - 如果这些类具有名称为{{1}的无法访问的成员} / begin

答案 1 :(得分:1)

几乎。

如果有效,则执行#1;如果有效则执行#2;如果无效,则执行#3#3是非常基本的标签调度/执行操作。

对于#3:

创建一个其他地方使用的命名空间。将它嵌入另一个。

在外部,放置一个=delete开始函数,它可以获取任何内容。

放入一个调用begin的辅助函数。

这将找到adl开始,否则删除开始。

失败模式是命名空间可以在其他地方使用;没有办法阻止它。