c++ - 为什么编译器在这种情况下会选择不正确的函数重载?

标签 c++ templates inheritance language-lawyer argument-dependent-lookup

我正在尝试 Sean Parent 在 GoingNative 2013 的演讲中展示的代码 - "Inheritance is the base class of evil". (上一张幻灯片中的代码位于 https://gist.github.com/berkus/7041546

我试图自己实现相同的目标,但我不明白为什么下面的代码不会像我预期的那样运行。

#include <boost/smart_ptr.hpp>
#include <iostream>
#include <ostream>

template <typename T>
void draw(const T& t, std::ostream& out)
{
    std::cout << "Template version" << '\n';
    out << t << '\n';
}

class object_t
{
public:
    template <typename T>
    explicit object_t (T rhs) : self(new model<T>(rhs)) {};

    friend void draw(const object_t& obj, std::ostream& out)
    {
        obj.self->draw(out);
    }

private:
    struct concept_t
    {
        virtual ~concept_t() {};
        virtual void draw(std::ostream&) const = 0;
    };

    template <typename T>
    struct model : concept_t
    {
        model(T rhs) : data(rhs) {};
        void draw(std::ostream& out) const
        {
            ::draw(data, out);
        }

        T data;
    };

    boost::scoped_ptr<concept_t> self;
};

class MyClass {};

void draw(const MyClass&, std::ostream& out)
{
    std::cout << "MyClass version" << '\n';
    out << "MyClass" << '\n';
}

int main()
{
    object_t first(1);
    draw(first, std::cout);

    const object_t second((MyClass()));
    draw(second, std::cout);

    return 0;
}

此版本处理打印 int很好,但在第二种情况下无法编译,因为编译器不知道如何使用 MyClassoperator<< .我不明白为什么编译器不会选择专门为 MyClass 提供的第二个重载.如果我更改 model::draw() 方法的名称并删除 ::,代码将编译并正常工作全局命名空间说明符,或者如果我将 MyClass 的绘制全局函数更改为完整的模板特化。

我得到的错误信息如下,之后是一堆candidate function not viable...

t76_stack_friend_fcn_visibility.cpp:9:9: error: invalid operands to binary expression ('std::ostream' (aka 'basic_ostream<char>') and 'const MyClass')
    out << t << '\n';
    ~~~ ^  ~
t76_stack_friend_fcn_visibility.cpp:36:15: note: in instantiation of function template specialization 'draw<MyClass>' requested here
            ::draw(data, out);
              ^
t76_stack_friend_fcn_visibility.cpp:33:9: note: in instantiation of member function 'object_t::model<MyClass>::draw' requested here
        model(T rhs) : data(rhs) {};
        ^
t76_stack_friend_fcn_visibility.cpp:16:42: note: in instantiation of member function 'object_t::model<MyClass>::model' requested here
    explicit object_t (T rhs) : self(new model<T>(rhs)) {};
                                         ^
t76_stack_friend_fcn_visibility.cpp:58:20: note: in instantiation of function template specialization 'object_t::object_t<MyClass>' requested here
    const object_t second((MyClass()));
                   ^

为什么选择全局绘图模板函数的模板版本而不是 MyClass 函数重载?是因为模板引用是贪心的吗?如何解决这个问题?

最佳答案

因为您在函数调用中使用了限定名称。 [temp.dep.candidate]:

For a function call that depends on a template parameter, the candidate functions are found using the usual lookup rules (3.4.1, 3.4.2, 3.4.3) except that:

  • For the part of the lookup using unqualified name lookup (3.4.1) or qualified name lookup (3.4.3), only function declarations from the template definition context are found.
  • For the part of the lookup using associated namespaces (3.4.2), only function declarations found in either the template definition context or the template instantiation context are found.

§3.4.2(别名 [basic.lookup.argdep]):

When the postfix-expression in a function call (5.2.2) is an unqualified-id, other namespaces not considered during the usual unqualified lookup (3.4.1) may be searched, and in those namespaces, namespace-scope friend function declarations (11.3) not otherwise visible may be found.

所以基本上 ADL 不适用,因为调用使用了限定 ID。
正如巴里展示的那样in his answer您可以通过使调用不合格来解决此问题:

void draw(std::ostream& out) const
{
    using ::draw;
    draw(data, out);
}

你必须添加一个 using - 在此之前的声明。否则不合格的名称查找将找到 model<>::draw成员函数在按升序搜索声明区域时优先,不再继续搜索。但不仅如此 - 因为 model<>::draw (这是一个类成员)发现我的不合格名称查找,ADL 被调用,[basic.lookup.argdep]/3:

Let X be the lookup set produced by unqualified lookup (3.4.1) and let Y be the lookup set produced by argument dependent lookup (defined as follows). If X contains

  • a declaration of a class member, or
  • a block-scope function declaration that is not a using-declaration, or
  • a declaration that is neither a function or a function template

then Y is empty. Otherwise Y is the set of declarations found in the namespaces associated with the argument types as described below.

因此,如果 using -declaration 提供了非限定名称查找找到的唯一声明将是全局 draw被引入声明区域 model::draw 的模板. 然后调用 ADL 并找到后来声明的 draw MyClass const& 的函数.

关于c++ - 为什么编译器在这种情况下会选择不正确的函数重载?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26821777/

相关文章:

c++ - 如何使用访问者从变体返回特定类型?

PHP 父子类数据库操作

c++ - 如何在一台机器上构建 vc9 和 vc10 版本的 Boost?

c++ - 二进制协议(protocol) - 字节交换技巧

c++ - 为什么非 const、非 int/enum 静态数据成员必须在定义之外初始化?

c++ - 返回两个用户输入值的函数

c++ - 将左值传递给 RValue 的参数

c++ - 使用继承和模板的数据库接口(interface)

具有继承类型问题的 Java Builder

Javascript继承实现问题