基于范围的for循环和ADL

6

C++0x标准工作草案(第6.5.4节)对于范围for循环中隐式调用begin()和end()的说明如下:

“begin”和“end”使用参数相关查找(3.4.2)进行查找。对于此名称查找,命名空间std是一个关联命名空间。

我理解的意思是,调用begin()和end()的重载分辨集包括以下所有内容:

  • 在范围for循环使用的位置作用域内的所有begin()和end()的重载(特别是全局命名空间中的所有重载将在作用域内)
  • 命名空间std中的所有begin()和end()的重载
  • 与其参数相关联的其他命名空间中的所有begin()和end()的重载

这样理解正确吗?

g++ 4.6的行为似乎与此解释不一致。 对于此代码:

#include <utility>

template <typename T, typename U>
T begin(const std::pair<T, U>& p); 

template <typename T, typename U>
U end(const std::pair<T, U>& p); 

int main()
{
    std::pair<int*, int*> p;
    for (int x : p)
        ;
}

它会给出以下错误:
adl1.cpp: In function 'int main()':
adl1.cpp:12:18: error: No match for 'begin(pair<int *, int *> &)'
adl1.cpp:12:18: candidate is:
/usr/local/lib/gcc/i686-pc-linux-
    gnu/4.6.0/../../../../include/c++/4.6.0/initializer_list:86:38: template<
        class _Tp> constexpr const _Tp * begin(initializer_list<_Tp>)
adl1.cpp:12:18: error: No match for 'end(pair<int *, int *> &)'
adl1.cpp:12:18: candidate is:
/usr/local/lib/gcc/i686-pc-linux-
    gnu/4.6.0/../../../../include/c++/4.6.0/initializer_list:96:36: template<
        class _Tp> constexpr const _Tp * end(initializer_list<_Tp>)

这表明它仅考虑 std 命名空间中的重载,而不考虑全局命名空间中的重载。
然而,如果我使用在全局命名空间中声明的自己的 pair 类,它可以编译通过:
template <typename T, typename U>
struct my_pair
{
    T first;
    U second;
};

template <typename T, typename U>
T begin(const my_pair<T, U>& p); 

template <typename T, typename U>
U end(const my_pair<T, U>& p); 

int main()
{
    my_pair<int*, int*> p;
    for (int x : p)
        ;
}

作为最后的测试,我尝试将 my_pair 放在一个独立的命名空间中:
namespace my
{

template <typename T, typename U>
struct my_pair
{
    T first;
    U second;
};

}

template <typename T, typename U>
T begin(const my::my_pair<T, U>& p); 

template <typename T, typename U>
U end(const my::my_pair<T, U>& p); 

int main()
{
    my::my_pair<int*, int*> p;
    for (int x : p)
        ;
}

而且我再次收到了错误信息:

adl3.cpp: In function 'int main()':
adl3.cpp:22:18: error: 'begin' was not declared in this scope
adl3.cpp:22:18: suggested alternative:
adl3.cpp:14:35:   'begin'
adl3.cpp:22:18: error: 'end' was not declared in this scope
adl3.cpp:22:18: suggested alternative:
adl3.cpp:17:33:   'end'

所以,它似乎只考虑了命名空间std和其他相关命名空间中的重载,而不考虑在调用点处范围内的重载(上面列表中的第一个要点)。
这是gcc的错误吗,还是我对标准的解释有误?
如果是后者,这是否意味着无法将std::pair对象视为range在range-based for循环中处理(没有重载std::begin()和std::end(),如果我没记错的话是不允许的)?

我建议将其重新标记为“foreach”,因为基于范围的for循环只是它的更冗长的术语。 - Collin Dauphinee
1
“这是否意味着在基于范围的for循环中无法将std :: pair对象视为范围进行处理?” 我怀疑不是这样,因为即使您已经误解了标准,C ++ 0x现在对函数模板进行了部分特化。您不允许重载std :: begin,但允许对其进行专门化。 - Steve Jessop
@HighCommander4:请定义“确定”。但不,我只是根据模糊的记忆工作。由于“类模板部分特化”在n3225中排名靠前,而没有“函数模板部分特化”,所以我猜它们没有实现,因此如果GCC的实现是正确的,那么你就会被卡住。 - Steve Jessop
@litb:除了命名空间std之外。但这没关系,因为问题并不是缺乏功能,而是标准(可能是正确的)认为您不应该允许使用标准类更改标准模板函数的行为。由于gcc无法在全局命名空间中找到重载,这是我们最后的和平希望,但只是因为编译器存在缺陷,所以不是标准的错。 - Steve Jessop
1
然而,实际上很少有人编写 using std::sort; sort(foo.begin(), foo.end()); 以获得算法的 ADL 过载版本。因此,通常只有当客户知道此功能时才会使用它。您说得对,在实践中存在不一致性,因为如果 Foo 只是一个非模板类,那么我们可以偷偷地添加 std::sort(Foo::iterator, Foo::iterator) 的完整特化,即使用户调用了完全限定的 std::sort(foo.begin(), foo.end()),所有用户也将获得该特化版本。 - Steve Jessop
显示剩余5条评论
1个回答

4

我最初报告这看起来像是gcc的一个bug。现在似乎即使for循环规范的这部分也不清楚,委员会已经开展了调查。

看起来范围-based for循环规则很快就要改变了:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3257.pdf

我不确定N3257中列出的哪个选项会被选择。


我能看出GCC是如何做到这一点的,因为我发现描述完全令人困惑。我之前在Usenet上发布了同样的问题。请参见https://groups.google.com/group/comp.lang.c++/browse_thread/thread/7d14b0e6184daaea - Johannes Schaub - litb
谢谢 Johannes。我已经相应地修改了我的答案。 - Howard Hinnant
规范的问题是在广泛使用之前尝试标准化时发生的典型例子。希望这个问题能够尽快得到解决。 - dsign

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接