实例化隐藏功能

时间:2016-10-06 13:52:48

标签: c++ c++11

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 ...时,要求编译器明确地使用基函数重载作为匹配之一,没有任何改变,它再次编译

只是从下面的答案中得出我所理解的结论,因为它们涵盖了所发生的事情的不同方面

  1. 编译器甚至不考虑基本过载。这是我没有看到的关键事实,因此所有的误解

  2. 这不是这种情况,但在重载决策期间选择最佳匹配仅实例化所选匹配

2 个答案:

答案 0 :(得分:5)

  

在第(2)点,当我调用foo()时,在我看来,编译器应该生成一个可能的重载列表。

Trueish。我们首先在foo中查找名称Child。我们找到Child::foo然后停止。我们只有一个候选人,这是一个可行的候选人,所以我们称之为。我们不会继续查看基类,因此永远不会考虑CrtpBase::foo。无论签名如何都是如此(如果CrtpBase::foo()占用intobj.foo(4)将无法编译,因为Child::foo()没有参与 - 即使是假设的电话到CrtpBase::foo(4)会很好。)

  

当我取消注释// using ...时,要求编译器明确地使用基函数重载作为匹配之一,没有任何改变,它再次编译

实际上并非你正在做的事情。 using-declaration 将名称CrtpBase::foo带入Child的范围,就像在那里声明一样。但随后void foo()的声明隐藏了重载(否则调用将是模糊的)。同样,fooChild的名称查找找到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; }
};

Live example

调用hello<int>.foo()会考虑hello::foo(std::string)并且不会实例化它。

致电hello<std::string>.foo("world");时会考虑hello::foo()并且不会实现它。