Начинается в общем случае плюс decltype с учетом локальной декларации использования
С++ 0x ranged-for loop имеет специальное исключение для обработки массивов (FDIS §6.5.4), и есть две функции: std:: begin и end, которые перегружены для обработки массивов или для выбора begin/концевые методы. Это заставляет меня полагать, что функция, принимающая общую последовательность, может быть записана в соответствии с поведением цикла с диапазоном:
template<class C>
void f(C &c) {
using std::begin;
using std::end;
do_something_with(begin(c), end(c));
}
Если в пространстве имен C будет указано "более конкретное" начало/конец, оно будет выбрано через ADL, иначе код "по умолчанию" будет равен std:: begin/end.
Тем не менее, существует причина, из-за которой имеет место специальное исключение. Если передать массив типа в пространстве имен с семантически различным началом/концом, который принимает указатель, формы массива std:: begin/end не выбраны:
namespace ns {
struct A {};
void begin(A*); // Does something completely different from std::begin.
}
void f_A() { // Imagine above f() called with an array of ns::A objects.
ns::A c[42];
using std::begin;
begin(c); // Selects ns::begin, not array form of std::begin!
}
Чтобы избежать этого, есть ли лучшее решение, чем писать мои собственные обертки начала/конца (которые используют ADL внутри) и вызывать их явно, а не как std:: begin или ADLized begin?
namespace my {
template<class T>
auto begin(T &c) // Also overload on T const &c, as std::begin does.
-> decltype(...) // See below.
{
using std::begin;
return begin(c);
}
template<class T, int N>
T* begin(T (&c)[N]) {
return c;
}
}
// my::end omitted, but it is analogous to my::begin.
template<class C>
void f(C &c) {
do_something_with(my::begin(c), my::end(c));
}
Однако, как показано выше эллипсисом, я даже не знаю, как написать my:: begin! Как я могу, для этого типа decltype, выбрать тип, который будет выбран через локальную декларацию использования и ADL?
Ответы
Ответ 1
Я столкнулся с такой же ситуацией при использовании кортежей:
template<typename Tuple>
auto f(Tuple&& tuple)
-> /* ??? */
{
using std::get;
return get<Idx>(tuple);
}
который принимает как std::tuple
, так и boost::tuple
, и принимает как lvalues, так и rvalues в отличие от template<typename... Types> auto f(std::tuple<Types...>& tuple) -> /* ??? */
.
Этот частный случай был решен с помощью класса признаков, который фактически предоставляется стандартом: std::tuple_element
. Как обычно, с классами признаков, идея состоит в том, что tuple
является протоколом, и все, что хочет соответствовать ему, предоставит специализацию, например. tuple_element
. Так что в моем случае решение уже существовало.
В вашем случае, если вы писали библиотеку, я бы рекомендовал написать (и документировать) такой класс признаков. В коде приложения или в других ситуациях я не уверен.
Ответ 2
Вы можете использовать специальные массивы самостоятельно. Тип массива (и должен быть для начала/конца работы) ElementType (&)[Size]
, поэтому, если вы перегружаете функцию, например:
template<class C, size_t S>
void f(C (&c)[S]) {
do_something_with(std::begin(c), std::end(c));
}
он должен вести себя особенно как цикл for.
На боковой ноте вам не нужны std::begin
и std::end
, тогда они тривиальны:
template<class C, size_t S>
void f(C (&c)[S]) {
do_something_with(c, c + S);
}
(может потребоваться листинг, я использовал его только с вещами, требующими указателей, а не с итераторами).
С другой стороны, функции begin
и end
, выполняющие указатели, довольно глупы. Если заостренный объект является коллекцией, скорее всего, они должны брать ссылку.