我想使用基于范围的for
来迭代UTF8编码的std::string
中的unicode代码点。我在全局命名空间中定义了自己的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
之外没有其他方法吗?
答案 0 :(得分:6)
用您自己的类型换行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
答案 1 :(得分:6)
N3337 6.5.4 / 1:
(...) begin-expr 和 end-expr 确定如下:
- 如果
_RangeT
是数组类型, begin-expr 和 end-expr 是__range
和__range + __bound
分别为(...);- 如果
_RangeT
是类类型, unquali fi ed-id sbegin
和end
是 在类_RangeT
的范围内查找,就好像通过类成员访问一样 查找(3.4.5),如果其中任何一个(或两个)发现至少一个声明, begin-expr 和 end-expr 分别为__range.begin()
和__range.end()
, 分别;- 否则, begin-expr 和 end-expr 为
begin(__range)
, 查看end(__range)
和begin
的{{1}}分别为end
和std
参数依赖查找(3.4.2)。出于此名称的目的 lookup,namespacestd::string
是一个关联的命名空间。
换句话说,它会调用begin
的{{1}}和end
成员函数(第二个列表项目符号)。正确的解决方案是提供一个包装类,如anthony的答案所示。
注意:如果您使用-std=c++1y
,则可以省略尾随的decltype。
您还可以编写一个typedef来减少输入:
typedef custom_string<std::string> cs;
for (char c : cs(t)) {
std::cout << c;
}
答案 2 :(得分:1)
最干净的方法,至少在使用时,这是为了特殊迭代而标记你的类型。
首先,一些机器:
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)};
}
接下来,我们发明一个标记,说“奇怪地迭代”,并重载开始/结束:
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>
将创建对左值的引用,并创建rvalue的移动副本(如果传递给它)。在迭代中,其返回值的生命周期将通过引用生命周期扩展来扩展。
您可以使用此技术执行
之类的操作for( char c : mark_type<reverse_iteration>(s) )
现在我们改为向后迭代,无论我们传入的容器如何。对于这样的结构,需要为rvalue创建“副本”:
for( char c: mark_type<reverse_iteration>(mark_type<strange_iteration>(s))
我们用菊花链连接标记。 Lifetime扩展仅适用于最外层的返回值,而我们在rvalue上的“创建副本和移动”基本上是手动终身扩展。
最后,上述代码中std::begin
的使用最好在返回值的ADL-admitting上下文中完成。创建一个这样的帮助程序命名空间:
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
中的adl_helper::adl_begin
,该for( a:b )
模仿begin
循环查找end
和{{1}}的方式触摸得更好(不完美,但更好)。
C ++ 1y可能会附带一些机制来消除对上述黑客的需求。
示例代码正在运行:http://ideone.com/RYvzD0