模拟基于范围的循环的开始/结束行为 [英] Simulating the range-based for loop's begin/end behavior

查看:140
本文介绍了模拟基于范围的循环的开始/结束行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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


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

    $ b $如果 _RangeT 是数组类型, begin-expr end-expr 是<$ c $ b
  • c> __ range 和 __ range + __bound ,其中 __ bound
    数组绑定。如果 _RangeT 是未知大小的数组或
    不完整类型的数组,则程序格式不正确;
  • 如果 _RangeT 是一个类类型,那么 unqualified-id begin end 在class _RangeT 的范围内查找,就像通过类成员访问
    lookup(3.4.5)一样, (或两者)至少发现一个
    声明,则 begin-expr end-expr __ range.begin()
    __ range.end();

  • 否则, begin -expr end-expr begin(__ range) end(__ range)开始和 end 分别在
    和相关的命名空间(3.4。 2)。 [注意:不执行普通的不合格的
    查找(3.4.1)。 - end note ]


是否可以模拟这个 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;
/ *语句* /
}
}

这个行为完全相同吗?

非答案包括对 std :: begin / std :: end (不处理第三个项目符号等等)和使用std :: begin;开始(范围); 因为,如果ADL for begin 发现一个与 std :: begin 。






b

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

模板< class T> int * begin(T&){return nullptr; }
模板< 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 {};
模板< typename T>
using base = std :: conditional_t< std :: is_class< T> {}&&不是std :: is_final< T> {},
T,空>;

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

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

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

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

//!第二步 - 这个过载比上面的要少。
模板< 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();}

//!步骤3
命名空间nested_detail {
void begin(),end();
模板< typename T>
constexpr auto begin_(T& a) - > decltype(begin(a)){return begin(a);}
模板< typename T>
constexpr auto end_(T& a) - > decltype(end(a)){return end(a);}
}
模板< 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);}
模板< 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);}
}

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

演示 。请注意,GCCs查找被打破,因为它不考虑在 TestMemType :: test_end / begin 中的 typename T :: begin code>。可以在 此处 找到解决方法草图。



步骤2中的检查要求类类型是可派生的,这意味着此方法不能正确处理 final 类或联合 - 如果这些成员具有名称 begin / end 的无法访问的成员。


Consider the specification of the range-based for loop's begin-expr and end-expr (N4140 [stmt.ranged]/p1). Given a range __range of type _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 ]

Is it possible to simulate this exact behavior in ordinary C++ code? i.e., can we write a magic_begin and a magic_end function template such that

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

and

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

always have the exact same behavior?

Non-answers include qualified calls to std::begin/std::end (doesn't handle the third bullet, among other things) and using std::begin; begin(range); because, among other things, that is ambiguous if ADL for begin finds an overload that's equally good as std::begin.


For illustration, given

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;

I want magic_begin(a)/magic_begin(b)/magic_begin(c)/magic_begin(d) to be a compile error, and magic_begin(e) to return (int*)nullptr.

解决方案

The following SFINAE-friendly approach seems to work as desired (see below for exceptions):

#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. Note that GCCs lookup is broken as it doesn't consider non-type names for typename T::begin in TestMemType::test_end/begin. A workaround sketch can be found here.

The check in step 2 requires that the class type be derivable, which implies that this method doesn't properly work with final classes or unions - if those have an inaccessible member with name begin/end.

这篇关于模拟基于范围的循环的开始/结束行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆