Я хочу использовать диапазон на основе for
перебирать кодовые точки Юникода в кодировке UTF8 std::string
, Я определил свой begin
а также end
в глобальном пространстве имен, кроме begin
а также end
в std
пространство имен является предпочтительным (то есть найденное ADL). Есть ли способ отдать предпочтение моим собственным функциям?
Пример:
const char* begin(const std::string& s) {
std::cout << "BEGIN";
return s.data();
}
const char* end(const std::string& s) {
std::cout << "END";
return s.data() + s.length();
}
int main() {
std::string s = "asdf";
for (char c : s)
std::cout << c;
}
Я хочу это напечатать BEGINENDasdf
(или же ENDBEGINasdf
) но это печатает asdf
,
Есть ли другой способ, кроме как сделать руководство for
используя квалифицированное имя?
Заворачивать std::string
в вашем собственном типе. Сделав его шаблоном, вы можете настроить любой существующий контейнер и добавить к нему собственную логику диапазона. Это даже не отличается от вашей первой попытки.
#include <string>
#include <iostream>
template <typename S>
struct custom_container {
S &s_;
custom_container (S &s) : s_(s) {}
auto begin() -> decltype(s_.begin()) {
std::cout << "BEGIN";
return s_.begin();
}
auto end() -> decltype(s_.end()) {
std::cout << "END";
return s_.end();
}
};
template <typename S>
custom_container make_container (S &s) {
return custom_container <S> (s);
}int main () {
std::string t = "asdf";
auto s = make_container(t);
for (char c : s) {
std::cout << c;
}
}
Выходы
BEGINENDasdf
N3337 6.5.4 / 1:
(…) начать-выр а также конец выраж определяются следующим образом:
— если
_RangeT
тип массива, начать-выр а также конец выраж являются
__range
а также__range + __bound
соответственно (…);— если
_RangeT
это тип класса, unquali фи эд-идентификаторsbegin
а такжеend
являются
посмотрел в поле зрения класса_RangeT
как будто по доступу члена класса
lookup (3.4.5), и если любой (или оба) находит хотя бы одно объявление,
начать-выр а также конец выраж являются__range.begin()
а также__range.end()
,
соответственно;— иначе, начать-выр а также конец выраж являются
begin(__range)
а также
end(__range)
соответственно гдеbegin
а такжеend
смотрят с
зависимый от аргумента поиск (3.4.2). Для целей этого имени
поиск, пространство именstd
это связанное пространство имен.
Другими словами, это будет называть std::string
«s begin
а также end
функции-члены (второй список). Правильное решение — предоставить класс-оболочку как Энтониответ подсказывает.
Примечание: если вы используете -std=c++1y
Вы можете опустить завершающий тип decl.
Вы также можете написать typedef, чтобы сделать его менее набирающим:
typedef custom_string<std::string> cs;
for (char c : cs(t)) {
std::cout << c;
}
Самый чистый способ сделать это, по крайней мере, на этапе использования, это разметить ваш тип с целью специальной итерации.
Сначала немного техники:
template<class Mark, class T>
struct marked_type {
T raw;
marked_type(T&& in):raw(std::forward<T>(in)) {}
};
template<typename Mark, typename T>
marked_type<Mark, T> mark_type( T&& t ) {
return {std::forward<T>(t)};
}
затем мы изобретаем метку, которая говорит «странно повторяться», и начинаем перегрузку begin / end:
struct strange_iteration {};
template<typename T>
auto begin( marked_type<strange_iteration, T> const& container )
-> decltype( std::begin(std::forward<T>(container.raw)) )
{
std::cout << "BEGIN";
using std::begin;
return begin(std::forward<T>(container.raw));
}
template<typename T>
auto end( marked_type<strange_iteration, T> const& container )
-> decltype( std::end(std::forward<T>(container.raw)) )
{
std::cout << "END";
using std::end;
return end(std::forward<T>(container.raw));
}
а затем в точке использования:
std::string s = "hello world";
for( char c : mark_type<strange_iteration>(s) ) {
std::cout << c;
}
std::cout << "\n";
с одной запиской, которую я написал mark_type
быть слишком общим.
Сейчас, mark_type<Foo>
создаст ссылки на lvalues и создаст перенесенную копию значения rvalue, если оно будет передано ему. На итерации время жизни возвращаемого значения будет увеличено путем увеличения продолжительности ссылки.
Вы можете использовать эту технику, чтобы сделать такие вещи, как
for( char c : mark_type<reverse_iteration>(s) )
где теперь мы вместо этого выполняем итерацию в обратном направлении, независимо от контейнера, который мы передали. «Создание копии» для rvalue необходимо для таких конструкций:
for( char c: mark_type<reverse_iteration>(mark_type<strange_iteration>(s))
где мы цепочки марки. Продление срока службы применяется только к самому внешнему возвращаемому значению, и наше «создание копии и перемещение» по значению r в основном является ручным продлением срока службы.
Наконец, std::begin
Использование в вышеприведенном коде лучше сделать в контексте ADL-допущения в возвращаемых значениях. Создайте пространство имен помощника следующим образом:
namespace adl_helper {
using std::begin; using std::end;
template<typename T>
auto adl_begin(T&& t)->decltype( begin(std::forward<T>(t)) ); // no implementation
template<typename T>
auto adl_end(T&& t)->decltype( end(std::forward<T>(t)) ); // no implementation
// add adl_cbegin, adl_rbegin etc in C++14
}
затем заменить std::begin
в decltype
S в моем коде выше с adl_helper::adl_begin
, который подражает как for( a:b )
петли найти begin
а также end
немного лучше (не идеально, но лучше).
C ++ 1y может поставляться с некоторыми механизмами для устранения необходимости вышеупомянутого взлома.
Пример кода работает: http://ideone.com/RYvzD0