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

时间:2014-11-08 20:33:48

标签: 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&#39;那么代码将编译并正常工作。将全局函数绘制为完整的模板专业化。

我得到的错误信息如下,之后是一堆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函数重载?是因为模板参考是贪婪的吗?如何解决这个问题?

2 个答案:

答案 0 :(得分:8)

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

  

对于依赖于模板参数的函数调用,   使用通常的查找规则找到候选函数(3.4.1,   3.4.2,3.4.3)除了:

     
      
  • 对于使用非限定名称查找(3.4.1)或限定名称查找(3.4.3)的查找部分,只有来自   找到模板定义上下文。
  •   
  • 对于使用关联命名空间(3.4.2)的查找部分,只能在模板定义中找到函数声明   找到了上下文或模板实例化上下文。
  •   

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

  

当函数调用(5.2.2)中的 postfix-expression unqualified-id 时,其他名称空间在通常可以搜索不合格的查找(3.4.1),在这些名称空间中,   命名空间范围的友元函数声明(11.3),否则没有   可以找到可见的。

因此,基本上ADL不适用,因为调用使用了qualified-id。
当Barry显示in his answer时,您可以通过使呼叫不合格来解决此问题:

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

您必须在此之前添加using - 声明。否则,在按升序搜索声明性区域时,非限定名称查找将首先找到model<>::draw成员函数,并且不会进一步搜索。但不仅如此 - 因为 model<>::draw(这是一个类成员)找到了我的非限定名称查找,ADL 被调用,[basic.lookup.argdep ] / 3:

  

X成为非限定查找(3.4.1)和   let Y是由参数依赖查找生成的查找集   (定义如下)。如果X包含

     
      
  • 类成员声明
  •   
  • 块范围函数声明,它不是 using-declaration
  •   
  • 既不是函数也不是函数模板的声明
  •   
     

然后Y为空。否则Y是在与之关联的名称空间中找到的声明集   参数类型如下所述。

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

答案 1 :(得分:3)

直接拨打::draw()时,您无法正确使用ADL。 (为什么?我实际上并不是特意知道,希望有人会进来向我解释 [编辑:见Columbo's answer以及为什么] )但是为了实际使用ADL ,你需要像draw那样对void draw(std::ostream& out) const { using ::draw; draw(data, out); } 进行无条件的调用:

draw(const MyClass&, std::ostream&)

这将正确找到重载{{1}}。