Seb*_*mer 11 c++ inheritance templates language-lawyer argument-dependent-lookup
我正在尝试Sean Parent在GoingNative 2013上的演讲中提出的代码 - "继承是邪恶的基础".(最后一张幻灯片的代码可在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;
}
Run Code Online (Sandbox Code Playgroud)
此版本处理印刷int精美,但未能在第二种情况下进行编译,因为编译器不知道如何使用MyClass带operator<<.我无法理解为什么编译器不会选择专门为其提供的第二个重载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()));
^
Run Code Online (Sandbox Code Playgroud)
为什么全局绘图模板函数的模板版本选择MyClass函数重载?是因为模板参考是贪婪的吗?如何解决这个问题?
因为您在函数调用中使用限定名称.[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是非限定id时,可以搜索在通常的非限定查找(3.4.1)期间未考虑的其他名称空间,并在这些名称空间中搜索名称空间范围的朋友函数声明( 11.3)可能没有其他可见的.
因此,基本上ADL不适用,因为调用使用了qualified-id.
正如Barry 在回答中所示,您可以通过使呼叫不合格来解决此问题:
void draw(std::ostream& out) const
{
using ::draw;
draw(data, out);
}
Run Code Online (Sandbox Code Playgroud)
你必须在using此之前添加一个-declaration.否则,非限定名称查找将model<>::draw在按升序搜索声明性区域时首先找到成员函数,并且不再搜索.但不仅如此 - 因为 model<>::draw(这是一个类成员)被发现我的非限定名称查找,ADL 没有被调用,[basic.lookup.argdep]/3:
让
X是由不合格查找(3.4.1)中产生的查找组并且让Y是由参数依赖查找产生的查找集(定义如下).如果X包含
- 集体成员的声明,或
- 块范围函数声明,它不是using声明,或
- 既不是函数也不是函数模板的声明
然后
Y是空的.否则Y是在与参数类型相关联的名称空间中找到的声明集,如下所述.
因此,如果using提供了-declaration,则由非限定名称查找找到的唯一声明将draw是引入声明性区域的全局模板model::draw.然后调用ADL并找到后面声明的draw函数MyClass const&.