考虑到:
#include <cassert>
#include <boost/range/irange.hpp>
#include <boost/range/algorithm.hpp>
int main() {
auto range = boost::irange(1, 4);
assert(boost::find(range, 4) == end(range));
}
这给出了:
main.cpp:8:37:错误:使用未声明的标识符'end'
考虑到如果你写using boost::end;
works just fine,这意味着boost::end
可见:
为什么ADL无效并在boost::end
表达式中找到end(range)
?如果它是故意的,背后的理由是什么?
为清楚起见,预期结果与使用std::find_if
和不合格end(vec)
的{{3}}中的结果类似。
答案 0 :(得分:10)
使用以下代码,编译器会抱怨没有开始/结束 找到&#34;
range_2
&#34;这是整数范围。我想整数范围 缺少ADL兼容性?
#include <vector>
#include <boost/range/iterator_range.hpp>
#include <boost/range/irange.hpp>
int main() {
std::vector<int> v;
auto range_1 = boost::make_iterator_range(v);
auto range_2 = boost::irange(0, 1);
begin(range_1); // found by ADL
end(range_1); // found by ADL
begin(range_2); // not found by ADL
end(range_2); // not found by ADL
return 0;
}
ADL无意找到
boost::begin()
和boost::end()
。在 事实上,Boost.Range特别采取预防措施来防范 通过声明,ADL找到boost::begin()
和boost::end()
它们在namespace boost::range_adl_barrier
中然后导出它们 从那里进入namespace boost
。 (这种技术称为&#34; ADL 屏障&#34;。)对于您的
range_1
,原因不合格begin()
和end()
调用工作是因为ADL不仅在命名空间中查找模板 声明了,但模板参数的命名空间 同样宣布。在这种情况下,range_1
的类型是boost::iterator_range<std::vector<int>::iterator>
。模板 参数在namespace std
(在大多数实现中),因此ADL找到std::begin()
和std::end()
(与boost::begin()
和boost::end()
不同using boost::begin;
,不要使用ADL屏障来防止被发现 ADL)。要编译代码,只需添加&#34;
using boost::end;
&#34;和 &#34;begin()/end()
&#34;,或明确限定您的boost::
来电 与&#34;begin
&#34;。
对于end
和begin(x)
的无限制通话,ADL的危险是双重的:
x
中,如果begin
具有(可能是默认的!)模板参数,或者在其实现中隐藏了基类,则ADL也会考虑模板参数及其基类的关联命名空间。这些关联的命名空间中的每一个都可能导致在参数依赖查找期间引入end
和namespace std
的许多重载。begin
中,end
和boost
函数模板不会针对每个容器单独重载,或以其他方式约束在所提供的容器的签名上。当另一个命名空间(例如std::iterator
)也提供类似的无约束函数模板时,重载解析将认为两者都是相等匹配,并且发生错误。以下代码示例说明了以上几点。
小型容器库
第一个要素是拥有一个容器类模板,它很好地包装在它自己的命名空间中,带有一个派生自begin
的迭代器,以及一般和无约束的函数模板end
和{{1} }。
#include <iostream>
#include <iterator>
namespace C {
template<class T, int N>
struct Container
{
T data[N];
using value_type = T;
struct Iterator : public std::iterator<std::forward_iterator_tag, T>
{
T* value;
Iterator(T* v) : value{v} {}
operator T*() { return value; }
auto& operator++() { ++value; return *this; }
};
auto begin() { return Iterator{data}; }
auto end() { return Iterator{data+N}; }
};
template<class Cont>
auto begin(Cont& c) -> decltype(c.begin()) { return c.begin(); }
template<class Cont>
auto end(Cont& c) -> decltype(c.end()) { return c.end(); }
} // C
小范围图书馆
第二个要素是使用另一组无约束的函数模板begin
和end
来创建一个范围库,它也包含在自己的命名空间中。
namespace R {
template<class It>
struct IteratorRange
{
It first, second;
auto begin() { return first; }
auto end() { return second; }
};
template<class It>
auto make_range(It first, It last)
-> IteratorRange<It>
{
return { first, last };
}
template<class Rng>
auto begin(Rng& rng) -> decltype(rng.begin()) { return rng.begin(); }
template<class Rng>
auto end(Rng& rng) -> decltype(rng.end()) { return rng.end(); }
} // R
通过ADL超载解决方案歧义
当尝试将迭代器范围放入容器时,麻烦就开始了,同时使用不合格的begin
和end
进行迭代:
int main()
{
C::Container<int, 4> arr = {{ 1, 2, 3, 4 }};
auto rng = R::make_range(arr.begin(), arr.end());
for (auto it = begin(rng), e = end(rng); it != e; ++it)
std::cout << *it;
}
rng
上依赖于参数的名称查找会发现begin
和end
的3次重载:来自namespace R
(因为{{1}从rng
开始(因为namespace C
模板参数rng
生活在那里)和Container<int, 4>::Iterator
(因为迭代器来自namespace std
)。 过载分辨率会将所有3次重载视为相等匹配,这会导致硬错误。
Boost通过将std::iterator
和boost::begin
放在内部命名空间中并使用指令将它们拉入封闭的boost::end
命名空间来解决这个问题。替代方案和IMO更直接的方式是来ADL保护类型(而不是函数),因此在这种情况下,boost
和Container
类模板。
Live Example With ADL barriers
有趣的是,ADL保护IteratorRange
和Container
- 在这种特殊情况下 - 足以让上述代码无误地运行,因为IteratorRange
和std::begin
会被调用,因为std::end
不受ADL保护。 这非常令人惊讶和脆弱。例如。如果std::iterator
的实现不再来自C::Container::Iterator
,则代码将停止编译。因此,最好在std::iterator
的任何范围内使用合格的来电R::begin
和R::end
,以免受此类拙劣的名称劫持。
另请注意,range-for曾经具有上述语义(将ADL至少namespace R
作为关联的命名空间)。这在N3257中讨论过,导致范围内的语义变化。当前范围 - 首先查找成员函数std
和begin
,这样就不会考虑end
和std::begin
,无论ADL障碍和std::end
的继承如何1}}。
std::iterator
答案 1 :(得分:7)
在boost/range/end.hpp
中,他们通过将end
置于range_adl_barrier
命名空间,然后using namespace range_adl_barrier;
将其置于boost
命名空间中来明确阻止ADL。
由于end
实际上并非来自::boost
,而是来自::boost::range_adl_barrier
,因此ADL找不到它。
他们的推理在boost/range/begin.hpp
中描述:
//使用ADL命名空间屏障来避免与其他不合格的内容模糊不清 //电话这对于C ++ 0x鼓励而言尤为重要 //对开始/结束的不合格调用。
没有举例说明这会导致问题,所以我只能理解他们所说的内容。
以下是我发明ADL如何引起歧义的例子:
namespace foo {
template<class T>
void begin(T const&) {}
}
namespace bar {
template<class T>
void begin(T const&) {}
struct bar_type {};
}
int main() {
using foo::begin;
begin( bar::bar_type{} );
}
live example。 foo::begin
和bar::begin
都是同样有效的函数,可以在该上下文中调用begin( bar::bar_type{} )
。
这可能就是他们所说的。他们boost::begin
和std::begin
在您using std::begin
类型boost
的情况下可能同样有效。通过将其放在boost
的子命名空间中,std::begin
被调用(并且自然地在范围上工作)。
如果命名空间begin
中的boost
不那么通用,那么它会更受欢迎,但这不是他们编写它的方式。
答案 2 :(得分:5)
那是因为boost::end
是inside an ADL barrier,然后是pulled in boost
at the end of the file。
但是,从cppreference's page on ADL开始(抱歉,我没有方便的C ++草稿):
1)忽略相关命名空间中的using-directive
这可以防止它被包含在ADL中。