比ADL更喜欢某些功能

时间:2013-12-14 05:48:22

标签: c++ argument-dependent-lookup

我想使用基于范围的for来迭代UTF8编码的std::string中的unicode代码点。我在全局命名空间中定义了自己的beginend,但begin命名空间中的endstd是首选(即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之外没有其他方法吗?

3 个答案:

答案 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 s beginend是   在类_RangeT的范围内查找,就好像通过类成员访问一样   查找(3.4.5),如果其中任何一个(或两个)发现至少一个声明,    begin-expr end-expr 分别为__range.begin()__range.end(),   分别;

     

- 否则, begin-expr end-expr begin(__range),   查看end(__range)begin的{​​{1}}分别为endstd   参数依赖查找(3.4.2)。出于此名称的目的   lookup,namespace std::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

相关问题