Condider以下代码:
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()
。 (此时编译应该失败,因为错误在body函数中,SFINAE不适用)然后编译器应该选择优先级匹配。
但是程序编译并显示CrtpBase::foo
未实例化。问题是为什么。编译器以某种方式知道Child::foo
将是最佳匹配并停止重载解析过程。
当我取消注释// using ...
时,要求编译器明确地使用基函数重载作为匹配之一,没有任何改变,它再次编译
只是从下面的答案中得出我所理解的结论,因为它们涵盖了所发生的事情的不同方面
编译器甚至不考虑基本过载。这是我没有看到的关键事实,因此所有的误解
这不是这种情况,但在重载决策期间选择最佳匹配仅实例化所选匹配
答案 0 :(得分:5)
在第(2)点,当我调用foo()时,在我看来,编译器应该生成一个可能的重载列表。
Trueish。我们首先在foo
中查找名称Child
。我们找到Child::foo
然后停止。我们只有一个候选人,这是一个可行的候选人,所以我们称之为。我们不会继续查看基类,因此永远不会考虑CrtpBase::foo
。无论签名如何都是如此(如果CrtpBase::foo()
占用int
,obj.foo(4)
将无法编译,因为Child::foo()
没有参与 - 即使是假设的电话到CrtpBase::foo(4)
会很好。)
当我取消注释
// using ...
时,要求编译器明确地使用基函数重载作为匹配之一,没有任何改变,它再次编译
实际上并非你正在做的事情。 using-declaration 将名称CrtpBase::foo
带入Child
的范围,就像在那里声明一样。但随后void foo()
的声明隐藏了重载(否则调用将是模糊的)。同样,foo
中Child
的名称查找找到Child::foo
并停止。如果此案例与前一案例不同,如果CrtpBase::foo()
使用不同的参数,那么将考虑(例如我的hyptohetical obj.foo(4)
调用上一段)。
答案 1 :(得分:4)
模板类实例可以在没有实例化方法的情况下实例化。
Child
不是模板类实例。它的方法总是被实例化。
简单地对模板类实例方法名执行重载解析 not 触发模板类方法实例化。如果未选择,则不会实例化。
此功能最初是在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()
并且不会实现它。