C++重载解析规则中存在漏洞?

8
请考虑以下代码:
#include <iostream>

namespace ns1
{
    struct A
    {
    };

    template <class T>
    std::ostream& operator << (std::ostream& os, const T& t)
    {
        return os << "ns1::print" << std::endl;
    }
}

namespace ns2
{
    template <class T>
    std::ostream& operator << (std::ostream& os, const T& t)
    {
        return os << "ns2::print" << std::endl;
    }

    void f (const ns1::A& a)
    {
        std::cout << a;
    }
}


int main()
{
    ns1::A a;

    ns2::f (a);

    return 0;
}

按照标准,编译会出现“模棱两可的重载错误”。

但是为什么?毕竟 A 命名空间中“同样好”的运算符应该具有优先权吧?难道没有逻辑上的理由吗?


8
为什么您认为 A 命名空间中的函数应该优先于调用函数 f 的名称空间中的函数呢?这种情况不可避免地会产生歧义。报错是唯一明智的选择。 - Cody Gray
因为创建该命名空间的人更清楚如何打印A? - cppalex
8
首先,它们是模板。如果创建 A 的人想要确保打印类型为 A 的对象的某种行为,他们将提供重载或专门化。这将解决此处的歧义。其次,命名空间可以多次打开和关闭,因此该函数甚至可能不是 A 的实现者提供的。 - Cody Gray
2个回答

10
如果您希望优先使用命名空间 A中的超载版本,则需要向其中添加一些内容,以使其实际上更好。例如,通过使其不是一个模板:
namespace ns1 
{
    std::ostream& operator<<(std::ostream&, const A& );
}

否则,如果两个命名空间中的函数模板完全等效,那么从概念上讲没有理由认为其中一个命名空间中的函数模板比另一个命名空间中的函数模板更好。毕竟,为什么A命名空间中的函数模板会比f命名空间中的函数模板“更好”呢?实现f的人难道不会“更懂”吗?仅依赖于函数签名可以避开这个问题。


0

如果您仔细阅读编译器错误,就会发现歧义错误不在ns1ns2中的operator<<版本之间,而是在来自ns1operator<<(os,const char*)实例化和来自namespace std的完全相同重载之间。后者被ADL拖入了std::ostream

最好的方法是使用@Barry的建议,在命名空间ns1中去模板化operator<<,并将与ns1::A相关的所有功能(例如f(A))添加到同一命名空间中:

#include <iostream>

namespace ns1
{
    struct A {};

    std::ostream& operator << (std::ostream& os, const A& t)
    {
        return os << "ns1::print" << std::endl;
    }

    void f (const A& a)
    {
        std::cout << a;
    }    
}

int main()
{
    ns1::A a;
    f(a); // rely on ADL to find ns1::operator<<(os, A)
}

实时示例

命名空间 ns1 然后通过 ADL 作为 class A 的更广泛接口。


1
是的和不是。如果你在字符串中使用SFINAE,那么cout << a仍然会在两个函数模板之间产生歧义。 - Barry
我倾向于简化OP的例子,将operator<<(std :: ostream&,const T&)更改为类似于g(const T&)的形式。 - Barry
@Barry 如果使用 print() 函数,就可以避免这种意外了,是的。 - TemplateRex

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