编写以下代码:
template <class Impl, class Cont>
struct CrtpBase
{
void foo()
{
cout << "Default foo\n";
Cont cont;
cont.push_back(10); // Going to fail if Cont::push_back doesn't exist
}
};
typedef std::unordered_map<int,int> ContType;
struct Child : public CrtpBase<Child, ContType>
{
typedef CrtpBase<Child, ContType> _Parent;
// using _Parent::foo // (1)
void foo() { cout << "Child\n"; }
};
int main()
{
Child obj;
obj.foo(); // (2)
return 0;
}我所坚持的是CrtpBase类实例化和不实例化的条件。
在第(2)点,当我调用foo()时,编译器应该生成一个可能重载的列表。这些将是Child::foo()和Child::_Parent::foo()。因此,必须实例化Child::_Parent::foo()。(此时编译应该失败,因为错误在主体函数中,SFINAE不适用),那么编译器应该选择优先级匹配。
但是,该程序编译并显示CrtpBase::foo没有实例化。问题是为什么。编译器不知何故知道Child::foo将是最佳匹配,并停止过载解析过程。
当我取消注释// using ...时,要求编译器显式地使用基函数重载作为匹配项之一,没有任何变化,它会再次编译。
只是想总结一下我从下面的答案中了解到的,因为它们涵盖了的不同方面
发布于 2016-10-06 14:06:42
在第(2)点,当我调用foo()时,编译器应该生成一个可能重载的列表。
真有道理。我们首先在foo中查找Child的名称。我们找到Child::foo然后停下来。我们只有一个候选人,这是一个可行的候选人,所以我们称之为。我们不再继续查看基类,因此从未考虑过CrtpBase::foo。不管签名如何,这都是正确的(如果CrtpBase::foo()使用int,obj.foo(4)将无法编译,因为Child::foo()不带参数--即使对CrtpBase::foo(4)的假设调用格式很好)。
当我取消注释
// using ...时,要求编译器显式地使用基函数重载作为匹配项之一,没有任何变化,它会再次编译。
这其实不是你要做的。using-声明将名称CrtpBase::foo带入Child的作用域,就好像它是在那里声明的一样。但是,void foo()的声明隐藏了该重载(否则调用将是模糊的)。因此,foo在Child中的名称查找会发现Child::foo并停止。与前一种情况不同的是,如果CrtpBase::foo()采用了不同的参数,那么就会考虑它(比如上一段中我的obj.foo(4)调用)。
发布于 2016-10-06 14:06:01
模板类实例可以在不实例化其方法的情况下实例化。
Child不是模板类实例。它的方法总是被实例化。
简单地对模板类实例方法名称执行重载解析不会触发模板类方法实例化。如果没有选择它,就不会实例化它。
这个特性最初是在C++的历史前添加的。它允许std::vector<T>有一个有条件地工作的operator<,这取决于T在SFINAE前的C++时代是否有operator<。
就更简单的情况而言:
template<class T>
struct hello {
void foo() { T t{}; t -= 2; }
void foo(std::string c) { T t{}; t += c; }
};实例化。
调用hello<int>.foo()会考虑hello::foo(std::string),而不会实例化它。
调用hello<std::string>.foo("world");会考虑hello::foo(),而不会实例化它。
https://stackoverflow.com/questions/39897877
复制相似问题