11

我尝试在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<<。我不明白为什么编译器不会选择专门为第二个过载提供的e MyClass。如果我更改model :: draw()方法的名称并从其主体中删除全局名称空间说明符,或者如果将MyClass的draw全局函数更改为完整的模板专用化,则代码编译并正常工作。

该错误消息我得到的是如下,之后是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())); 
       ^

一堆为什么全球绘制模板功能模板版本choosen在MyClass的函数重载?是否因为模板引用是贪婪的?如何解决这个问题?

+0

我试过你用MSVC13的代码,它编译得很好,在第一种情况下使用int版本,在第二种情况下使用int版本。你应该添加关于你使用的编译器 – Christophe 2014-11-08 20:50:58

+0

我使用ng --version ng版本3.5.0(tags/RELEASE_350/final)。 – 2014-11-08 20:56:42

+0

[另一个问题具有相同的原理](http://stackoverflow.com/questions/8501294/different-behavior-for-qualified-and-unqualified-name-lookup-for-template/8501421#8501421) – 2014-11-10 02:30:16

回答

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)为不合格-id,在通常的不合格查找(3.4.1)期间未考虑的其他名称空间可能会被搜索,并且在这些名称空间中, 命名空间范围的朋友函数声明(11.3)不是其他方式可能会发现可见。

所以基本上ADL不适用,因为调用使用合格的ID。
百里显示in his answer你可以通过调用不合格解决此问题:

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

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

X是通过非限定查找(3.4.1)和 产生的查找集合让Y成为由参数相关查找产生的查找集合 (定义如下)。如果X包含

  • 类成员的声明,或
  • 一个块范围函数声明为不是using声明,或
  • 声明既不是函数或功能模板

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

因此,如果using -declaration提供不合格的名称查找发现将是引入的model::draw声明区全球draw模板的唯一声明。 然后调用ADL并找到后面为MyClass const&声明的draw函数。

+1

你知道吗第一个要点的理由是什么? – 2014-11-08 20:58:39

+0

@SebastianKramer这就是所谓的“两阶段查找”。考虑'void foo(void *);模板 void bar(T/* dummy * /){foo(0); } void foo(int); int main(){bar(0); }'大概模板的作者打算'bar'来调用'void *'超载而不是'int'超载。 – 2014-11-08 21:10:59

+0

@ T.C。你确定它使用'using'声明是符合标准的吗?我对[namespace.udecl]/11 – Columbo 2014-11-08 21:20:48

3

当您直接拨打::draw()时,您无法正确使用ADL。 (为什么?我实际上并不知道,希望有人会进来向我解释这个[编辑:参见Columbo's answer与为什么])但是为了实际使用ADL,您需要进行非限定的调用draw像这样:

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

这将正确地找到过载draw(const MyClass&, std::ostream&)

+0

扩展搜索范围有用,谢谢。然而,我正在寻找这种行为背后的理性。 – 2014-11-08 21:01:36

+0

@SebastianKramer很好,ADL不能应用在定义上下文中,因为我们不知道参数或候选函数在那里。 ADL确实适用于实例化上下文(出于相同的原因,[ADL在模板之外是可取的](http://stackoverflow.com/questions/8111677/what-is-argument-dependent-lookup-aka -adl或 - 柯尼希 - 查找))。这使我们保持现状。 – 2014-11-10 03:00:58

+0

尽管我还不明白为什么3.4.1查找不应该应用于依赖函数调用的实例化上下文中。 – 2014-11-10 03:02:47

相关问题