Вызывать начало и конец с помощью директивы-указателя?

Установленная идиома для вызова swap:

using std::swap
swap(foo, bar);

Таким образом, swap может быть перегружен для пользовательских типов вне пространства имен std.

Должны ли мы вызывать begin и end тем же способом?

using std::begin;
using std::end;
some_algorithm(begin(some_container), end(some_container));

Или нужно просто написать:

some_algorithm(std::begin(some_container), std::end(some_container));

Ответы

Ответ 1

Использование using -declaration, как это, является правильным способом IMO. Это также то, что стандарт делает с диапазоном для цикла: если нет присутствующих членов begin или end, тогда он будет вызывать begin(x) и end(x) с std в качестве ассоциированного пространства имен (то есть он найдет std::begin и std::end, если ADL не находит нечлены begin и end).

Если вы обнаружите, что запись using std::begin; using std::end; все время утомительна, вы можете использовать функции adl_begin и adl_end ниже:

namespace aux {

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

template<class T>
auto adl_begin(T&& x) -> decltype(begin(std::forward<T>(x)));

template<class T>
auto adl_end(T&& x) -> decltype(end(std::forward<T>(x)));

template<class T>
constexpr bool is_array()
{
    using type = typename std::remove_reference<T>::type;
    return std::is_array<type>::value;
}

} // namespace aux

template<class T,
         class = typename std::enable_if<!aux::is_array<T>()>::type>
auto adl_begin(T&& x) -> decltype(aux::adl_begin(std::forward<T>(x)))
{
    using std::begin;
    return begin(std::forward<T>(x));
}

template<class T,
         class = typename std::enable_if<!aux::is_array<T>()>::type>
auto adl_end(T&& x) -> decltype(aux::adl_end(std::forward<T>(x)))
{
    using std::end;
    return end(std::forward<T>(x));
}

template<typename T, std::size_t N>
T* adl_begin(T (&x)[N])
{
    return std::begin(x);
}

template<typename T, std::size_t N>
T* adl_end(T (&x)[N])
{
    return std::end(x);
}

Этот код довольно чудовищный. Надеюсь, с С++ 14 это может стать менее загадочным:

template<typename T>
concept bool Not_array()
{
    using type = std::remove_reference_t<T>;
    return !std::is_array<type>::value;
}

decltype(auto) adl_begin(Not_array&& x)
{
    using std::begin;
    return begin(std::forward<Not_array>(x));
}

decltype(auto) adl_end(Not_array&& x)
{
    using std::end;
    return end(std::forward<Not_array>(x));
}

template<typename T, std::size_t N>
T* adl_begin(T (&x)[N])
{
    return std::begin(x);
}

template<typename T, std::size_t N>
T* adl_end(T (&x)[N])
{
    return std::end(x);
}

Ответ 2

Отказ от ответственности: для педантичных типов (или педантов, если вы хотите быть педантичными...), я обычно отношусь к слову "перегрузка" здесь как "Создать функции, которые имеют имена begin и end и do using std::begin; using std::end;.", который, поверьте мне, не утомительно для меня писать вообще, но очень трудно читать и излишне читать. :p.


В основном я дам вам возможные варианты использования такой техники, а затем и мой вывод.

Случай 1 - ваши методы begin и end не действуют, как те из стандартных контейнеров

Одна из ситуаций, когда вам может потребоваться перегрузить функции std::begin и std::end, является использование методов begin и end вашего типа другим способом, отличным от предоставления доступа, подобного итератору к элементам объекта и хотите, чтобы перегрузки std::begin и std::end вызывали методы начала и конца, используемые для итерации.

struct weird_container {
   void begin() { std::cout << "Start annoying user." }
   void end() { std::cout << "Stop annoying user." }

   iterator iter_begin() { /* return begin iterator */ }
   iterator iter_end() { /* return end iterator */ }
};


auto begin(weird_container& c) {
   return c.iter_begin();
}

auto end(weird_container& c) {
   return c.iter_end();
}

Однако вы не должны и не должны делать такую ​​сумасшедшую вещь, как range-for break, если она используется с объектом weird_container, согласно правилам range-for, weird_container::begin() и weird_container::end() методы будут найдены до автономных вариантов функций.

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

Методы Case 2 - begin и end не определены вообще

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

struct good_ol_type {
   ...
   some_container& get_data();
   ...
};

auto begin(good_ol_type& x) {
   return x.get_data().begin();
}

auto end(good_ol_type& x) {
   return x.get_data().end();
}

Это позволит вам использовать некоторые отличные функции в good_ol_type (алгоритмы, диапазон и т.д.), не изменяя при этом свой интерфейс! Это согласуется с рекомендацией Херба Саттера о расширении функциональности типов с помощью функций, отличных от других.

Это хороший случай, когда вы действительно хотите перегрузить std:;begin и std::end.

Заключение

Как я ни разу не видел, чтобы кто-то делал что-то вроде первого случая (кроме моего примера), тогда вы действительно хотите использовать то, что вы предложили, и перегрузите std::begin и std::end, где это применимо.


Я не включил здесь случай, когда вы определили методы begin и end и begin и end, которые выполняют разные действия, чем методы. Я считаю, что такая ситуация надуманна, плохо сформирована и/или сделана программистом, у которого не было большого опыта в анализе отладчика или чтении новых ошибок шаблона.

Ответ 3

Если ваш some_container является стандартным контейнером, std:: prefix не нужен

#include <iostream>
#include <vector>
#include <algorithm>
int main(){ 
       std::vector<int>v { 1, 7, 1, 3, 6, 7 };
       std::sort( begin(v), end(v) ); // here ADL search finds std::begin, std::end
}

Ответ 4

документация swap указывает, что идиома, которую вы называете, является обычной практикой в ​​библиотеке stl

Многие компоненты стандартной библиотеки (внутри std) обмениваются вызовами в неквалифицированный способ разрешить пользовательские перегрузки для нефакторных типов для вызова вместо этой общей версии: пользовательские перегрузки swap объявленные в том же пространстве имен, что и тип, для которого они предоставлены получить выбранный через зависящий от аргумента поиск по этому родовому версия.

В документации для begin и end нет такой вещи.

По этой причине вы можете определенно использовать

using std::begin;
using std::end;
some_algorithm(begin(some_container), end(some_container));

но вы должны знать, что это соглашение, которое не применяется к, например, стандартные алгоритмы, но только для вашего кода.