7

我想使用基于范围for来迭代 UTF8 编码中的 unicode 代码点std::string。我已经在全局命名空间中定义了我自己的beginand ,但命名空间中的and是首选(即由 ADL 找到的那些)。有什么方法可以让我更喜欢我自己的功能吗?endbeginendstd

例子:

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除了使用限定名称来编写手册之外,没有其他方法吗?

4

3 回答 3

6

Wrap std::string in your own type. By making it a template you can customise any existing container and add your own range logic to it. It's not even that different from your first attempt.

#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;
    }
}

Outputs

BEGINENDasdf

于 2013-12-22T06:01:31.780 回答
6

N3337 6.5.4/1:

(...) begin-exprend-expr确定如下:

— 如果_RangeT是数组类型,begin-exprend-expr分别是 __rangeand __range + __bound,(...);

— 如果_RangeT是类类型,则在类的范围内查找unqualified-idbegin,就像通过类成员访问查找(3.4.5)一样,如果其中一个(或两者)找到至少一个声明,则 begin-exprend-expr分别是和;end_RangeT__range.begin()__range.end()

— 否则,begin-exprend-expr分别是begin(__range)end(__range),其中beginend使用参数相关查找 (3.4.2) 进行查找。出于此名称查找的目的,命名空间std是一个关联的命名空间。

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

注意:如果你使用-std=c++1y你可以省略尾随的 decltype。

您还可以编写 typedef 以减少键入:

typedef custom_string<std::string> cs;

for (char c : cs(t)) {
    std::cout << c;
}
于 2013-12-22T06:25:47.773 回答
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
}

然后将我上面代码std::begin中的s 替换为,它模拟了循环如何找到和更好地触摸(不完美,但更好)。decltypeadl_helper::adl_beginfor( a:b )beginend

C++1y 可能带有一些机制来消除对上述 hack 的需要。

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

于 2013-12-26T07:33:02.257 回答