1

我正在考虑使用 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>’

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

4

2 回答 2

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 段确定如果应用上一条规则产生多个结果,则类型推导失败。

于 2013-02-20T04:19:19.730 回答
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 是肉。

于 2013-02-20T05:47:59.323 回答