为非数组重载std :: begin()和std :: end()

眠りネ*_*ネロク 4 c++ templates overloading using-declaration c++11

假设我有以下Data课程:

struct Data {
   char foo[8];
   char bar;
};
Run Code Online (Sandbox Code Playgroud)

和以下函数,my_algorithm需要一对char *(类似于STL算法):

void my_algorithm(char *first, char *last);
Run Code Online (Sandbox Code Playgroud)

对于Datafoo数据成员,而不是my_algorithm()像这样调用:

Data data;
my_algorithm(data.foo, data.foo + 8);
Run Code Online (Sandbox Code Playgroud)

我可以使用std::begin()std::end()便利功能模板:

my_algorithm(std::begin(data.foo), std::end(data.foo));
Run Code Online (Sandbox Code Playgroud)

我想实现类似于Databar数据成员的功能。也就是说,而不是写:

my_algorithm(&data.bar, &data.bar + 1);
Run Code Online (Sandbox Code Playgroud)

我想写一些像:

my_algorithm(begin(data.bar), end(data.bar));
Run Code Online (Sandbox Code Playgroud)

因此,在这种情况下,我定义了以下两个普通(非模板)函数:

char* begin(char& c) { return &c; }
char*   end(char& c) { return &c + 1; }
Run Code Online (Sandbox Code Playgroud)

这样我就可以编写如下代码:

Data data;

using std::begin;
using std::end;

my_algorithm(begin(data.foo), end(data.foo)); // ok - std::begin()/std::end()
my_algorithm(begin(data.bar), end(data.bar)); // Error!!!
Run Code Online (Sandbox Code Playgroud)

使用using上面的声明,我期望std::begin()/ std::end()::begin()/ ::end()分别处于同一重载集中。由于函数::begin()::end()是后一个调用的完美匹配,并且它们不是模板,因此,我希望最后一个调用my_algorithm()能够匹配它们。但是,根本不考虑普通功能。结果,编译失败,因为std::begin()std::end()调用不匹配。

基本上,后面的调用就像我写的一样:

my_algorithm(begin<>(data.bar), end<>(data.bar));
Run Code Online (Sandbox Code Playgroud)

也就是说,重载解析过程仅考虑功能模板(即std::begin()/ std::end()),而不考虑普通功能(即不::begin()/ ::end())。

它仅如预期,如果我完全有资格呼叫工作::begin()/ ::end()

my_algorithm(::begin(data.bar), ::end(data.bar));
Run Code Online (Sandbox Code Playgroud)

我在这里想念什么?

Jus*_*tin 6

让我们得到一个完整的,可复制的示例:

#include <iterator>

char* begin(char& c) { return &c; }
char*   end(char& c) { return &c + 1; }

namespace ns {
    void my_algorithm(char *first, char *last);

    void my_function() {
        using std::begin;
        using std::end;

        char c = '0';
        my_algorithm(begin(c), end(c));
    }
}
Run Code Online (Sandbox Code Playgroud)

当您对begin(c)和进行非限定调用时end(c),编译器将进行非限定名称查找的过程(在cppreference的参数相关的查找页面上进行了介绍)。

对于常规的不合格名称查找,此过程大致从当前所在的名称空间开始(::ns在这种情况下),并且仅在找不到特定名称的情况下才移出名称空间。

如果函数调用是不合格的,因为它是在这里与begin(c)end(c)参数依赖查找可以发生,这发现在相同的命名空间的类型的功能的论点宣布为无功能,通过延伸通过找到重载集合的过程“相关联的命名空间。”

但是,在这种情况下,char它是基本类型,因此依赖于参数的查找不允许我们查找全局::begin::end函数。

对于基本类型的参数,关联的名称空间和类的集合为空

cppreference:依赖于参数的查找

相反,因为我们已经有了using std::begin; using std::end;,编译器已经可以看到功能begin(...)end(...)-namely那些在命名空间中定义::std-without有一个命名空间,从搬出::ns::。因此,编译器使用这些函数,并且编译失败。


值得一提的是,using std::begin; using std::end;也找到自定义块的编译器::begin::end即使你是里面放置它们::ns


您可以做的是编写自己的beginend

#include <iterator>

namespace ns {
    char* begin(char& c) { return &c; }
    char*   end(char& c) { return &c + 1; }

    template <typename T>
    auto begin(T&& t) {
        using std::begin;
        // Not unbounded recursion if there's no `std::begin(t)`
        // or ADL `begin(t)`, for the same reason that our
        // char* begin(char& c); overload isn't found with
        // using std::begin; begin(c);
        return begin(t);
    }

    template <typename T>
    auto end(T&& t) {
        using std::end;
        return end(t);
    }

    void my_algorithm(char *first, char *last);

    void my_function() {
        char c = '0';
        my_algorithm(ns::begin(c), ns::end(c));
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 我假定基本类型包括全局名称空间,但是现在我实际阅读了该部分,它显然不添加任何内容:“对于基本类型的参数,关联的名称空间和类的集合为空”。 (2认同)
  • 太冗长了,很难回答这个答案。关于ADL的长话以及关于ADL在这里无关紧要的结论。另一个答案很明确。为什么需要这样的帮手... (2认同)