2013-12-14 33 views
7

我想使用基于范围的for迭代UTF8编码std::string中的unicode代码点。我已经在全局命名空间中定义了我自己的beginend,但std命名空间中的beginend(即ADL发现的那些)是优选的。有什么方法可以选择我自己的功能吗?倾向于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

注:您的基本假设是错误的:没有ADL这里涉及到,'s'是'的std :: string'所以'为(字符C:S)'的行为就像使用_member_形式's.begin()'和's.end()',而不是非成员形式'begin(s)'和'end(s)'(参见remyabel的回答) –

+3

@gx_ yes ,下面的一些答案提到了这一点。我把它颠倒过来,认为非成员'begin'首先尝试,成员'begin'第二。 – uk4321

+0

@ uk4321你可能感兴趣的[n3257](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3257.pdf)。 – 2013-12-24 00:24:25

回答

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

6

N3337 6.5.4/1:

(...)开始-EXPR最终EXPR确定如下:

- 如果_RangeT是数组类型,开始-EXPR最终EXPR是 分别__range__range + __bound,(...);

- 如果_RangeT是一个类类型, unquali音响ED-ID小号beginend是 在_RangeT类的范围看起来像是在由类成员访问 查找(3.4.5),和如果(或二者)连接NDS至少一个声明, 开始-EXPR最终EXPR分别__range.begin()__range.end(), ;

- 否则,开始-EXPR最终EXPR分别begin(__range)end(__range),其中beginend中查找与 参数相关的查找(3.4.2)。对于名称 的查找,名称空间std是一个关联的名称空间。

所以,换句话说,它会调用std::stringbeginend成员函数(第二项目符号列表)。正确的解决方案是提供一个包装类,如anthony的答案所示。

注意:如果您使用-std=c++1y,则可以省略结尾的decltype。

你也可以写一个typedef使它少打字:

typedef custom_string<std::string> cs; 

for (char c : cs(t)) { 
    std::cout << c; 
} 
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>将创建左值的引用,并创建右值的移动副本(如果传递给它)。在迭代中,其返回值的生命周期将通过引用生命周期扩展进行扩展。

您可以使用此技术做的事情一样

for(char c : mark_type<reverse_iteration>(s)) 

现在在哪里,我们不是重复倒退,无论我们在通过容器。在“副本创建”为右值是需要同样的结构这个:

for(char c: mark_type<reverse_iteration>(mark_type<strange_iteration>(s)) 

我们在哪里菊花链的标记。生命周期扩展只适用于最外层的返回值,我们在右值的“创建副本和移动”基本上是手动生存期延长。

最后,在上面的代码中使用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 
} 

然后用adl_helper::adl_begin替换decltype小号std::begin在我上面的代码,它模拟for(a:b)回路是如何找到beginend触摸更好的(不完美,但更好)。

C++ 1y可能会附带一些机制来消除对上述黑客的需求。

示例代码运行:http://ideone.com/RYvzD0