Имитация диапазона для начала и конца цикла

Рассмотрим спецификацию диапазона для цикла begin-expr и end-expr (N4140 [stmt.ranged]/p1). Учитывая диапазон __range типа _RangeT,

begin-expr и end-expr определяются следующим образом:

  • if _RangeT - тип массива, begin-expr и end-expr - __range и __range + __bound, соответственно, где __bound равно связанный массив. Если _RangeT - массив неизвестного размера или массив неполного типа, программа плохо сформирована;
  • Если _RangeT - это тип класса, то неквалифицированные-идентификаторы begin и end просматриваются в области класса _RangeT, как если бы доступ к члену класса поиск (3.4.5), и если либо (или оба) найдут хотя бы один объявления, begin-expr и end-expr являются __range.begin() и __range.end(), соответственно;
  • в противном случае begin-expr и end-expr равны begin(__range) и end(__range) соответственно, где begin и end просматриваются в связанные пространства имен (3.4.2). [Примечание: Обычный неквалифицированный поиск (3.4.1) не выполняется. -end note]

Можно ли смоделировать это точное поведение в обычном С++-коде? т.е. можно написать шаблон функции magic_begin и a 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) для возврата (int*)nullptr.

Ответы

Ответ 1

Следующий подход, благоприятный для 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);}

Демо. Обратите внимание, что поиск GCC нарушен, поскольку он не учитывает имена не-типа для typename T::begin в TestMemType::test_end/begin. Обходной эскиз можно найти здесь.

Для проверки на шаге 2 требуется, чтобы тип класса был выводимым, что означает, что этот метод не работает с классами или объединениями final, если они имеют недопустимый член с именем begin/end.

Ответ 2

Почти.

Выполнение # 1, если оно работает, и если не # 2, если оно работает, а если нет # 3 - это довольно простой тег dispatching/sfinae.

Для # 3:

Создайте пространство имен, которое больше нигде не используется. Гнездо в другом.

В внешнем, поместите функцию =delete begin, которая принимает что-либо.

Поместите вспомогательную функцию, которая вызывает в ней begin.

Это найдет начало adl, и в противном случае удаленные начнутся.

Режим отказа - это то, что пространства имен могут использоваться где-то в другом месте; нет способа предотвратить это.