2015-08-30 11 views
14

Rozważmy określenie zakres oparty na Loop rozpocząć lista_wyrażeń_sortowania i końcowego wyrażenie (N4140 [stmt.ranged]/P1). Biorąc pod uwagę zakres __range typu _RangeT,symulujących koniec zachowanie zakres oparty na pętli rozpocznijmy/

rozpocząć lista_wyrażeń_sortowania i końcową lista_wyrażeń_sortowania są określone w następujący sposób:

  • jeśli _RangeT jest typu tablicą rozpocząć lista_wyrażeń_sortowania i koniec-wyr. to odpowiednio __range i __range + __bound, gdzie __bound jest związana tablica. Jeśli _RangeT jest tablicą o nieznanym rozmiarze lub macierzą niekompletnego typu, program jest źle sformułowany;
  • jeśli _RangeT to typ klasy, niewykwalifikowany ID s begin i end porównywane są z zakresu klasy _RangeT jakby dostępu członka klasy przeglądowej (3.4.5) i jeśli jedna (lub obu) znajduje się co najmniej jeden deklaracja rozpocząć lista_wyrażeń_sortowania i końcową lista_wyrażeń_sortowania__range.begin() i __range.end(), odpowiednio;
  • inaczej rozpocząć lista_wyrażeń_sortowania i końcową lista_wyrażeń_sortowaniabegin(__range) i end(__range), odpowiednio, gdzie begin i end porównywane są z przynależne nazw (3.4.2). [Uwaga: Zwykłe nieuwzględnione badanie (3.4.1) nie jest wykonywane. - koniec uwaga]

Czy to możliwe, aby symulować ten dokładnie zachowanie zwykłego kodu C++? to znaczy, możemy napisać magic_begin i szablon magic_end funkcja taka, że ​​

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

i

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

zawsze mają dokładnie ten sam problem?

Non-odpowiedzi obejmują wykwalifikowanych połączeń do std::begin/std::end (nie zajmuje trzecią kulę, między innymi) i using std::begin; begin(range); ponieważ, między innymi, że jest niejednoznaczne jeśli ADL dla begin stwierdzi przeciążenie, który jest równie dobry jak std::begin .


Dla ilustracji, zważywszy

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; 

Chcę magic_begin(a)/magic_begin(b)/magic_begin(c)/magic_begin(d) być błąd kompilacji, a magic_begin(e) wrócić (int*)nullptr.

+0

Masz na myśli, że 'magic_end' jest błędem kompilacji dla' b', prawda? – Columbo

+0

@ Columbo Cóż, jeśli podążamy za specyfikacją opartą na zasięgu, to oba są błędami. Ale jestem zadowolony z tego, że "przynajmniej jeden z" magic_begin' i 'magic_end' powoduje błąd". –

+0

Och, przepraszam! Błędne odczytanie drugiego punktu. – Columbo

Odpowiedz

11

Poniższa SFINAE przyjazne podejście wydaje się działać zgodnie z oczekiwaniami (patrz poniżej wyjątkami):

#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. Zwróć uwagę, że wyszukiwanie GCC jest zepsute, ponieważ nie uwzględnia nazw nietypowych dla typename T::begin w TestMemType::test_end/begin. Szkic obejścia można znaleźć here.

Czek w kroku 2 wymaga, aby typ zajęć będzie wyprowadzić, co oznacza, że ​​ta metoda nie działa prawidłowo z final klas lub związków - jeśli te mają niedostępnych członka z nazwą begin/end.

+0

... i związki. (I typy nieklasyczne, ale to łatwe do naprawienia.) Bardzo fajne. –

+0

@ T.C. Ups, przepraszam. Naprawiono ten nieklazyczny bit inny niż tablicowy. – Columbo

1

Prawie.

Wykonanie # 1, jeśli działa, a jeśli nie, to 2 jeśli działa, a jeśli nie, to 3 jest całkiem podstawowym ćwiczeniem wysyłania znaczników/sfinae.

Dla # 3:

Tworzenie nazw, który jest używany nigdzie indziej. Umieść go w innym.

Na zewnętrznej stronie umieść funkcję początkową =delete, która ma wszystko.

Umieść funkcję pomocniczą, która wywołuje w niej begin.

To pozwoli znaleźć adl, a w przeciwnym razie rozpocznie się usuwanie.

Tryb awarii polega na tym, że przestrzenie nazw mogą być używane gdzie indziej; nie ma sposobu, aby temu zapobiec.

+0

Cóż, numer 1 jest banalny, ale nie jestem pewien, jak mogę idealnie symulować # 2, odkładając na chwilę # 3 na chwilę. W szczególności, # 2 mówi, że jeśli klasa ma * dowolny * członek dowolnego rodzaju o nazwie 'begin' lub' end', niezależnie od typu lub dostępności, to próbujesz wykonać wywołanie członka i nie próbujesz wykonywać darmowych wywołań funkcji . –

+0

Jeśli chodzi o # 3, również pomyślałem o tym pomyśle. Domyślam się, że musi to być "void begin (...) = delete;", abyśmy nie powodowali niejednoznaczności z chciwymi szablonowymi przeciążeniami znalezionymi przez ADL. To nadal się zepsuje, jeśli ADL znajdzie "początek (...);", ale biorąc pod uwagę, że taka funkcja jest prawie całkowicie bezużyteczna, myślę, że nie jestem tym specjalnie zatroskany. –

+0

@ t.c. hmm. Więc członek var 'begin' będzie działał? Sizeof (T :: begin)? członek, funkcja, typedef ...Praca w tym przypadku zawiera błąd w kompilacji, ponieważ możemy najpierw sprawdzić, czy 't.begin()' kompiluje i nie robi tego sprawdzenia. Np. Znajdź wyrażenie pseudo-sfinae i przejdź do 3, jeśli się nie powiedzie, i nie skompiluj, jeśli mu się uda * lub * jeśli nie uda się skompilować. – Yakk

Powiązane problemy