2015-08-30 13 views
14

Hãy xem xét các đặc điểm kỹ thuật của dãy dựa trên vòng lặp for của bắt đầu-exprcuối expr (N4140 [stmt.ranged]/p1). Với một loạt __range loại _RangeT,Mô phỏng cuối hành vi phạm dựa trên vòng lặp for của begin/

bắt đầu-exprcuối expr được xác định như sau:

  • nếu _RangeT là một loại mảng, bắt đầu-exprend-expr__range__range + __bound, tương ứng, trong đó __bound là mảng bị ràng buộc. Nếu _RangeT là một mảng có kích thước không xác định hoặc một mảng loại không đầy đủ, chương trình không đúng định dạng;
  • nếu _RangeT là một loại lớp, các không đủ tiêu chuẩn-id s beginend đang nhìn lên trong phạm vi của lớp _RangeT như thể bằng cách truy cập thành viên lớp tra cứu (3.4.5), và nếu một trong hai (hoặc cả hai) tìm thấy ít nhất một khai báo , bắt đầu-exprend-expr__range.begin()__range.end(), tương ứng;
  • khác, bắt đầu-exprcuối exprbegin(__range)end(__range), tương ứng, nơi beginend đang nhìn lên trong các không gian tên kèm theo (3.4.2). [Lưu ý: Thông thường không đủ tiêu chuẩn tra cứu (3.4.1) không được thực hiện. - cuối note]

Có thể để mô phỏng chính xác hành vi này trong C++ bình thường? ví dụ, chúng ta có thể viết một magic_beginmagic_end hàm mẫu mà

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 */ 
    } 
} 

luôn có hành vi chính xác giống nhau không?

Non-câu trả lời bao gồm điện thoại đủ điều kiện để std::begin/std::end (không xử lý viên đạn thứ ba, trong số những thứ khác) và using std::begin; begin(range); bởi vì, trong số những thứ khác, đó là mơ hồ nếu ADL cho begin tìm thấy một tình trạng quá tải đó đều tốt như std::begin .


Để minh hoạ, trao

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; 

Tôi muốn magic_begin(a)/magic_begin(b)/magic_begin(c)/magic_begin(d) là một lỗi biên dịch, và magic_begin(e) trở (int*)nullptr.

+0

Ý bạn là, bạn muốn 'magic_end' là lỗi biên dịch cho' b', phải không? – Columbo

+0

@Columbo Vâng, nếu chúng ta đang theo dõi đặc điểm kỹ thuật dựa trên phạm vi, thì cả hai sẽ là lỗi. Nhưng tôi hài lòng với "ít nhất một trong các' magic_begin' và 'magic_end' dẫn đến lỗi". –

+0

Ồ, xin lỗi! Đã đọc sai dấu đầu dòng thứ hai. – Columbo

Trả lời

11

Cách tiếp cận SFINAE thân thiện sau dường như làm việc như mong muốn (xem dưới đây để biết trường hợp ngoại lệ):

#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. Lưu ý rằng tra cứu của GCC bị hỏng vì nó không xem xét tên không phải kiểu cho typename T::begin trong TestMemType::test_end/begin. Bạn có thể tìm thấy bản phác thảo cách giải quyết here.

Vui lòng cung trong bước 2 đòi hỏi kiểu lớp được derivable, mà ngụ ý rằng phương pháp này không đúng cách làm việc với final lớp hoặc đoàn thể - nếu những có một thành viên không thể tiếp cận với tên begin/end.

+0

... và công đoàn. (Và loại không phải lớp, nhưng đó là dễ dàng để sửa chữa.) Rất tốt đẹp mặc dù. –

+0

@ T.C. Rất tiếc, xin lỗi. Đã sửa lỗi bit không thuộc mảng đó. – Columbo

1

Hầu như.

Làm # 1 nếu nó hoạt động, và nếu không # 2 nếu nó hoạt động, và nếu không # 3 là một bài tập khá cơ bản gửi/sfinae.

Đối với # 3:

Tạo vùng tên không được sử dụng ở nơi nào khác. Làm tổ trong một cái khác.

Ở bên ngoài, đặt một hàm bắt đầu =delete lấy bất kỳ thứ gì.

Đặt chức năng trợ giúp gọi số begin trong đó.

Điều đó sẽ tìm thấy adl bắt đầu và nếu không thì bắt đầu đã xóa.

Chế độ lỗi là không gian tên có thể được sử dụng ở nơi khác; không có cách nào để ngăn chặn nó.

+0

Vâng, # 1 là tầm thường, nhưng tôi không chắc chắn rằng làm thế nào tôi hoàn toàn có thể mô phỏng # 2, đặt sang một bên # 3 cho thời điểm này. Đặc biệt, # 2 cho biết nếu lớp có * bất kỳ thành viên nào của bất kỳ loại nào được gọi là 'bắt đầu' hoặc' kết thúc', bất kể loại hoặc khả năng truy cập, thì bạn cố thực hiện cuộc gọi thành viên và không thực hiện cuộc gọi hàm miễn phí . –

+0

Như # 3, tôi cũng nghĩ về ý tưởng đó. Tôi đoán nó sẽ phải là 'void bắt đầu (...) = xóa;', để chúng tôi không gây ra sự mơ hồ với quá tải templated tham lam được tìm thấy bởi ADL. Điều này vẫn phá vỡ nếu ADL tìm thấy một 'bắt đầu (...);', nhưng cho rằng một hàm như vậy gần như hoàn toàn vô dụng, tôi đoán tôi không quá lo ngại về điều đó. –

+0

@ t.c. hmm. Vì vậy, thành viên var 'bắt đầu' sẽ' sizeof (T :: bắt đầu) 'làm việc? thành viên, chức năng, typedef ...Làm việc trong trường hợp này bao gồm không biên dịch được, vì trước tiên chúng ta có thể kiểm tra nếu 't.begin()' biên dịch và không thực hiện kiểm tra đó. Tức là, tìm một biểu thức giả-sfinae, và tiếp tục 3 nếu nó thất bại, và không biên dịch nếu nó thành công * hoặc * nếu nó không biên dịch được. – Yakk

Các vấn đề liên quan