这个C ++ CRTP代码应该编译,如果是这样,应该怎么做?

时间:2013-02-20 02:54:05

标签: c++ templates inheritance overloading crtp

我正在考虑使用CRTP类来帮助重载,并想知道下面的代码会做什么:

#include <iostream>
#include <typeinfo>

template <class TDerived>
class FunctionImplementation
{
};

class AbstractFunction
{
};

class BaseFunction : public AbstractFunction, public FunctionImplementation<BaseFunction>
{
};

class DerivedFunction : public BaseFunction, public FunctionImplementation<DerivedFunction>
{
};

template <class TDerived>
void foo(const FunctionImplementation<TDerived>& function) {
    std::cout << "In foo for " << typeid(TDerived).name() << std::endl;
}

int main() {
    BaseFunction base;
    DerivedFunction derived;

    foo(base);
    foo(derived);
}

在OS X上使用GCC 4.2,它不能编译:

overload.cpp: In function ‘int main()’:
overload.cpp:31: error: no matching function for call to ‘foo(DerivedFunction&)’

在同一系统上使用Clang 4.0,它会在运行时编译并执行“自然”操作:

In foo for 12BaseFunction
In foo for 15DerivedFunction

使用Visual C ++ 2010,它也可以编译但运行方式不同:

In foo for class BaseFunction
In foo for class BaseFunction

最后,Linux上的GCC 4.7.2无法编译,但会提供更完整且相当权威的错误消息:

overload.cpp: In function ‘int main()’:
overload.cpp:31:16: error: no matching function for call to ‘foo(DerivedFunction&)’
overload.cpp:31:16: note: candidate is:
overload.cpp:22:6: note: template<class TDerived> void foo(const FunctionImplementation<TDerived>&)
overload.cpp:22:6: note:   template argument deduction/substitution failed:
overload.cpp:31:16: note:   ‘DerivedFunction’ is an ambiguous base class of ‘const FunctionImplementation<TDerived>’

哪个是对的?我不是导航语言标准的专家......

2 个答案:

答案 0 :(得分:3)

在这种情况下,我相信gcc是对的。您要求编译器为您执行类型推导,问题是类型DerivedFunction的给定参数不是直接FunctionImplementation<TDerived>,因此必须执行转换。此时,转化列表包括FunctionImplementation<BaseFunction>(通过BaseFunction)和FunctionImplementation<DerivedFunction>(直接)。这两个选择之间没有排序,因此编译器会出现歧义。

该标准在§14.8.2.1[temp.deduct.call] / 4,5

中对此进行了处理
  

(第4段)通常,推论过程试图找到模板参数值,使得推导出的A与A相同(在如上所述转换类型A之后)。但是,有三种情况可以产生差异:

     

[...]

     

如果P是一个类而P的形式为simple-template-id,则转换后的A可以是推导出的A的派生类。同样,如果P是指向一个类的形式simple-template-的指针id,转换后的A可以是指向推导出的A所指向的派生类的指针。

     

(第5段)只有在类型扣除否则失败的情况下,才考虑这些替代方案。如果它们产生多于一个可能的推导A,则类型推导失败。

在第4段中,它允许类型推导选择参数类型的基础,在这种情况下有2个这样的基数。第5段确定如果先前规则的应用产生多个结果,则类型推导失败。

答案 1 :(得分:1)

好的,下面的答案是错误的。我一直相信阅读13.3.1p7

  

在候选者是函数模板的每种情况下,使用模板参数推导(14.8.3,14.8.2)生成候选函数模板特化。然后以通常的方式将这些候选人作为候选职能处理。

模板参数推导使用适当的重载解析机制在语法上可能的特化(函数的函数重载解析等)中进行选择。

事实证明并非如此:模板参数推导有其自己的,非常有限的规则集,它们坚持完全匹配(步长cv限定符和解除引用等),只允许派生类到模板化 - 这里将基本参数转换视为一种特殊情况 - 并且该特殊情况明确禁止使用函数重载解析来处理任何歧义。

所以对于正确的答案,见上文。我在这里留下这个答案,因为它正在上升,让我相信我不是唯一一个错误的人:

foo(derived)的重载解析正在查找课程FunctionImplementation<T>中的Derived声明。类Derived没有该模板的成员范围声明,因此组合其基类的递归查找结果,在其层次结构中产生两个特化:

Derived
:   Base
    :   AbstractFunction
    ,   FunctionImplementation<Base>
,   FunctionImplementation<Derived>

考虑在进行名称查找时在基类派生层次结构中找到声明的深度意味着在没有静默影响以前的结果的情况下,不能将任何名称或基础添加到类中使用多重继承的派生类。相反,C ++拒绝为您挑选并提供using-declarations以明确声明哪个基类使用(在本例中为tepmlate)名称是您要引用的名称。

这方面的标准是10.2,p3-5是肉。