Перегрузка std :: begin () и std :: end () для не-массивов - PullRequest
4 голосов
/ 03 июня 2019

Предположим, у меня есть следующий Data класс:

struct Data {
   char foo[8];
   char bar;
};

и следующая функция my_algorithm, которая принимает пару char * (аналогично алгоритму STL):

void my_algorithm(char *first, char *last);

Для Data foo члена данных вместо вызова my_algorithm(), например:

Data data;
my_algorithm(data.foo, data.foo + 8);

Я могу использовать шаблоны удобных функций std::begin() и std::end():

my_algorithm(std::begin(data.foo), std::end(data.foo));

Я бы хотел достичь чего-то похожего на Data элемент данных *1025*. То есть вместо того, чтобы писать:

my_algorithm(&data.bar, &data.bar + 1);

Я хотел бы написать что-то вроде:

my_algorithm(begin(data.bar), end(data.bar));

Поэтому для этого случая я определил две следующие обычные (не шаблонные) функции:

char* begin(char& c) { return &c; }
char*   end(char& c) { return &c + 1; }

Чтобы я мог написать код, подобный следующему:

Data data;

using std::begin;
using std::end;

my_algorithm(begin(data.foo), end(data.foo)); // ok - std::begin()/std::end()
my_algorithm(begin(data.bar), end(data.bar)); // Error!!!

При объявлении using выше я бы ожидал, что std::begin() / std::end() и ::begin() / ::end() будут находиться в одном и том же наборе перегрузки соответственно. Так как функции ::begin() и ::end() идеально подходят для последнего вызова и не являются шаблонами, я ожидал, что последний вызов my_algorithm() будет соответствовать им. Однако обычные функции вообще не рассматриваются. В результате компиляция не удалась, потому что std::begin() и std::end() не совпадают для вызова.

По сути, последний вызов действует так, как если бы я написал вместо этого:

my_algorithm(begin<>(data.bar), end<>(data.bar));

То есть, в процессе разрешения перегрузки рассматриваются только шаблоны функций (т.е. std::begin() / std::end()), а не обычные функции (т.е. не ::begin() / ::end()).

Это работает только так, как и ожидалось, если я полностью квалифицирую звонки на ::begin() / ::end():

my_algorithm(::begin(data.bar), ::end(data.bar));

Что мне здесь не хватает?

Ответы [ 2 ]

6 голосов
/ 03 июня 2019

Давайте получим полный, воспроизводимый пример:

#include <iterator>

char* begin(char& c) { return &c; }
char*   end(char& c) { return &c + 1; }

namespace ns {
    void my_algorithm(char *first, char *last);

    void my_function() {
        using std::begin;
        using std::end;

        char c = '0';
        my_algorithm(begin(c), end(c));
    }
}

Когда вы делаете неквалифицированный вызов begin(c) и end(c), компилятор проходит процесс поиска неквалифицированного имени (описано на зависимой от аргумента странице поиска в cppreference ).

Для обычного поиска без квалификационного имени процесс примерно начинается в пространстве имен, в котором вы сейчас находитесь - ::ns вв этом случае - и перемещать пространство имен только в том случае, если вы не нашли конкретное имя.

Если вызов функции неквалифицирован, как здесь с begin(c) и end(c), в зависимости от аргументаможет произойти поиск , который находит свободные функции, объявленные в том же пространстве имен, что и типы аргументов функций, в процессе расширения набора перегрузки путем поиска «связанных пространств имен».

В этом случаеоднако char является фундаментальным типом, поэтому поиск, зависящий от аргументов, не позволяет нам найти глобальные функции ::begin и ::end.

Для аргументаЕсли базового типа нет, то связанный с ним набор пространств имен и классов будет пустым

cppreference: зависимый от аргумента поиск

Вместо этого, как мыуже есть using std::begin; using std::end;, компилятор уже видит возможные функции для begin(...) и end(...) - именно те, которые определены в пространстве имен ::std - без необходимости перемещать пространство имен из ::ns в ::.Таким образом, компилятор использует эти функции, и компиляция завершается неудачей.


Стоит отметить, что using std::begin; using std::end; также блокирует компилятор от поиска пользовательских ::begin и ::end, даже если вы должны были разместитьони внутри ::ns.


Вместо этого вы можете написать свои собственные begin и end:

#include <iterator>

namespace ns {
    char* begin(char& c) { return &c; }
    char*   end(char& c) { return &c + 1; }

    template <typename T>
    auto begin(T&& t) {
        using std::begin;
        // Not unbounded recursion if there's no `std::begin(t)`
        // or ADL `begin(t)`, for the same reason that our
        // char* begin(char& c); overload isn't found with
        // using std::begin; begin(c);
        return begin(t);
    }

    template <typename T>
    auto end(T&& t) {
        using std::end;
        return end(t);
    }

    void my_algorithm(char *first, char *last);

    void my_function() {
        char c = '0';
        my_algorithm(ns::begin(c), ns::end(c));
    }
}
0 голосов
/ 03 июня 2019

Заголовок вопроса: " Перегрузка std :: begin ()". Перегрузка возможна только в пределах одной области. То есть нельзя перегружать имена из разных областей. В другом объеме мы можем только приложить усилия, чтобы помочь поиску имени. По сути, здесь объявление «using std :: begin» скрывает :: begin в коде вопроса. См. S.Lippman для справки:

функции, которые являются членами двух разных пространств имен, не перегружают друг друга.

Область применения декларации. Имена, введенные в объявлении using, подчиняются нормальным правилам области видимости Объекты с тем же именем, определенные во внешней области видимости, скрыты.

Как только параметр имеет тип char, а char является фундаментальным типом - поиск, зависящий от аргумента, не должен приниматься во внимание - как упомянуто в комментариях - нет связанного пространства имен с фундаментальными типами. Опять вопрос был: «Чего мне не хватает?» - поэтому ответ сосредоточен только на причинах - рекомендации могут быть слишком широкими.

...