Ответ 1
Существует несколько подходов, каждый из которых имеет свои плюсы и минусы. Ниже три подхода с анализом затрат и результатов.
ADL через пользовательский не-член begin()
/end()
Первая альтернатива предоставляет не-членные шаблоны функций begin()
и end()
внутри пространства имен legacy
для модификации требуемой функциональности на любом классе или шаблоне класса, который может ее предоставить, но имеет, например, неправильные соглашения об именах. Затем код вызова может полагаться на ADL, чтобы найти эти новые функции. Пример кода (на основе комментариев от @Xeo):
// LegacyContainerBeginEnd.h
namespace legacy {
// retro-fitting begin() / end() interface on legacy
// Container class template with incompatible names
template<class C>
auto begin(Container& c) -> decltype(c.legacy_begin())
{
return c.legacy_begin();
}
// similarly for begin() taking const&, cbegin(), end(), cend(), etc.
} // namespace legacy
// print.h
template<class C>
void print(C const& c)
{
// bring into scope to fall back on for types without their own namespace non-member begin()/end()
using std::begin;
using std::end;
// works for Standard Containers, C-style arrays and legacy Containers
std::copy(begin(c), end(c), std::ostream_iterator<decltype(*begin(c))>(std::cout, " ")); std::cout << "\n";
// alternative: also works for Standard Containers, C-style arrays and legacy Containers
for (auto elem: c) std::cout << elem << " "; std::cout << "\n";
}
Преимущества: согласованное и краткое соглашение о вызове, которое работает полностью в целом
- работает для любого стандартного контейнера и пользовательских типов, которые определяют члены
.begin()
и.end()
- работает для массивов C-стиля
- может быть модифицирован для работы (также для диапазонов циклов!) для любого шаблона класса
legacy::Container<T>
, у которого нет элементов.begin()
иend()
без изменения исходных кодов.
Минусы: требуется использование объявлений во многих местах
-
std::begin
иstd::end
должны быть введены в каждую явную область вызова в качестве параметров возврата для массивов в стиле C (потенциальная ошибка для заголовков шаблонов и общая неприятность).
ADL через пользовательский не-член adl_begin()
и adl_end()
Второй вариант заключается в том, чтобы инкапсулировать объявления-объявления предыдущего решения в отдельное пространство имен adl
, предоставляя не-членные шаблоны функций adl_begin()
и adl_end()
, которые также могут быть найдены через ADL. Пример кода (на основе комментариев @Yakk):
// LegacyContainerBeginEnd.h
// as before...
// ADLBeginEnd.h
namespace adl {
using std::begin; // <-- here, because otherwise decltype() will not find it
template<class C>
auto adl_begin(C && c) -> decltype(begin(std::forward<C>(c)))
{
// using std::begin; // in C++14 this might work because decltype() is no longer needed
return begin(std::forward<C>(c)); // try to find non-member, fall back on std::
}
// similary for cbegin(), end(), cend(), etc.
} // namespace adl
using adl::adl_begin; // will be visible in any compilation unit that includes this header
// print.h
# include "ADLBeginEnd.h" // brings adl_begin() and adl_end() into scope
template<class C>
void print(C const& c)
{
// works for Standard Containers, C-style arrays and legacy Containers
std::copy(adl_begin(c), adl_end(c), std::ostream_iterator<decltype(*adl_begin(c))>(std::cout, " ")); std::cout << "\n";
// alternative: also works for Standard Containers, C-style arrays and legacy Containers
// does not need adl_begin() / adl_end(), but continues to work
for (auto elem: c) std::cout << elem << " "; std::cout << "\n";
}
Преимущества: согласованное соглашение о вызове, которое работает полностью в целом
- те же плюсы, что и для предложения @Xeo +
- повторяющиеся объявления использования были инкапсулированы (DRY)
Минусы: немного подробный
-
adl_begin()
/adl_end()
не является столь же кратким, какbegin()
/end()
- он, возможно, также не является идиоматическим (хотя и явным)
- ожидающий вывод типа С++ 14, также будет загрязнять пространство имен с помощью
std::begin
/std::end
ПРИМЕЧАНИЕ. Не уверен, что это действительно улучшает предыдущий подход.
Явная квалификация std::begin()
или std::end()
везде
Как только дословность begin()
/end()
была отклонена, почему бы не вернуться к квалифицированным вызовам std::begin()
/std::end()
? Пример кода:
// LegacyIntContainerBeginEnd.h
namespace std {
// retro-fitting begin() / end() interface on legacy IntContainer class
// with incompatible names
template<>
auto begin(legacy::IntContainer& c) -> decltype(c.legacy_begin())
{
return c.legacy_begin();
}
// similary for begin() taking const&, cbegin(), end(), cend(), etc.
} // namespace std
// LegacyContainer.h
namespace legacy {
template<class T>
class Container
{
public:
// YES, DOCUMENT REALLY WELL THAT THE EXISTING CODE IS BEING MODIFIED
auto begin() -> decltype(legacy_begin()) { return legacy_begin(); }
auto end() -> decltype(legacy_end()) { return legacy_end(); }
// rest of existing interface
};
} // namespace legacy
// print.h
template<class C>
void print(C const& c)
{
// works for Standard Containers, C-style arrays as well as
// legacy::IntContainer and legacy::Container<T>
std::copy(std::begin(c), std::end(c), std::ostream_iterator<decltype(*std::begin(c))>(std::cout, " ")); std::cout << "\n";
// alternative: also works for Standard Containers, C-style arrays and
// legacy::IntContainer and legacy::Container<T>
for (auto elem: c) std::cout << elem << " "; std::cout << "\n";
}
Преимущества: согласованное соглашение о вызове, которое работает почти в целом
- работает для любого стандартного контейнера и пользовательских типов, которые определяют члены
.begin()
и.end()
- работает для массивов C-стиля
Минусы: немного подробный и дооснащение не является общим и проблемой сохранения
-
std::begin()
/std::end()
является немного более подробным, чемbegin()
/end()
- может быть обновлен только для работы (также для диапазонов циклов!) для любого класса
LegacyContainer
, у которого нет элемента.begin()
иend()
(и для которых нет исходного кода!), предоставляя явные специализации шаблонов функций, не являющихся членамиbegin()
иend()
вnamespace std
- можно обновить только на шаблонах классов
LegacyContainer<T>
, добавив в него исходные текстыLegacyContainer<T>
(которые доступны для шаблонов) функции-членыbegin()
/end()
. Тройкаnamespace std
здесь не работает, потому что шаблоны функций не могут быть частично специализированы.
Что использовать?
Подход ADL через не-член begin()
/end()
в собственном пространстве имен контейнера является идиоматическим подходом С++ 11, особенно для универсальных функций, которые требуют дооснащения устаревших классов и шаблонов классов. Это та же самая идиома, что и для пользовательских функций, не являющихся членами swap()
.
Для кода, который использует только стандартные контейнеры или массивы C-стиля, std::begin()
и std::end()
можно было бы вызывать везде, не вводя объявления-объявления, за счет более подробных вызовов. Этот подход можно даже модернизировать, но для этого требуется задействовать namespace std
(для типов классов) или исходных модификаций исходного кода (для шаблонов классов). Это можно сделать, но не стоит проблем с сохранением.
В неродном коде, где данный контейнер известен во время кодирования, можно даже полагаться только на ADL для стандартных контейнеров и явно квалифицировать std::begin
/std::end
для массивов в стиле C. Он теряет согласованность вызовов, но экономит на использовании объявлений.