Вызывать начало и конец с помощью директивы-указателя?
Установленная идиома для вызова 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));
но вы должны знать, что это соглашение, которое не применяется к, например, стандартные алгоритмы, но только для вашего кода.