3

我一直在尝试为定点类型实现一个复数类,其中乘法运算的结果类型将是输入类型的函数。我需要具有可以将复数乘以复数以及复数乘以实数的功能。

这本质上是代码的简化版本。其中 A 是我的复杂类型。

template<typename T1, typename T2> struct rt {};

template<> struct rt<double, double> { 
    typedef double type;
};
//forward declaration
template<typename T> struct A;

template<typename T1, typename T2>
struct a_rt {
    typedef A<typename rt<T1,T2>::type> type;
};

template <typename T>
struct A {
    template<typename T2>
    typename a_rt<T,T2>::type operator*(const T2& val) const {
        typename a_rt<T,T2>::type ret;
        cout << "T2& called" << endl;
        return ret;
    }
    template<typename T2>
    typename a_rt<T,T2>::type operator*(const A<T2>& val) const {
        typename a_rt<T,T2>::type ret;
        cout << "A<T2>& called" << endl;
        return ret;
    }
};

TEST(TmplClassFnOverload, Test) {
    A<double> a;
    A<double> b;
    double c;
    a * b;
    a * c;
}

代码无法编译,因为编译器试图用and实例化a_rt模板。我不知道幕后发生了什么,因为我想编译器应该选择更专业的,所以只会作为参数实例化。doubleA<double>operator*(A<double>&)a_rt<double, double>

请您向我解释为什么这不起作用?如果这是一个限制,我应该如何解决这个问题。

谢谢一吨!

unittest.cpp: In instantiation of 'a_rt<double, A<double> >':
unittest.cpp:198:   instantiated from here 
unittest.cpp:174: error: no type named 'type' in 'struct rt<double, A<double> >' 

更新

编译器似乎对以下更改感到满意。我在这里缺少一些微妙之处。感谢能够引导我了解编译器在这两种情况下所做的事情的人。

    template<typename T2>
    A<typename rt<T,T2>::type> operator*(const T2& val) const {
        A<typename rt<T,T2>::type> ret;
        cout << "T2& called" << endl;
        return ret;
    }
    template<typename T2>
    A<typename rt<T,T2>::type> operator*(const A<T2>& val) const {
        A<typename rt<T,T2>::type> ret;
        cout << "A<T2>& called" << endl;
        return ret;
    }
4

2 回答 2

4

在 C++ 中解析函数调用分为五个阶段:

  1. 名称查找:这会找到两个版本 operator*
  2. 模板参数推导:这将应用于步骤 1 中找到的所有函数)
  3. 重载决议:将选择最佳匹配
  4. 访问控制:实际上是否可以调用最佳匹配(即它不是私有成员)
  5. virtuality:如果涉及虚函数,可能需要在 vtable 中查找

首先请注意,永远不会推断出返回类型。您根本无法重载返回类型。模板参数operator*被推导出来,然后替换到返回类型模板中。

那么通话中会发生什么a * b;?首先,这两个版本operator*都推导出了它们的论点。对于第一个重载,T2推断为存在A<double>,而对于第二个重载,则T2解析为double。如果有多个重载,标准说:

14.7.1 隐式实例化 [temp.inst] 第 9 条

如果以涉及重载解析的方式使用函数模板或成员函数模板特化,则将隐式实例化特化的声明 (14.8.3)。

因此,在生成候选函数集时,在参数推导结束时(因此在重载决议之前)模板被实例化,并且由于rt没有嵌套的type. 这就是为什么不会选择更专业的第二个模板的原因:重载决议不会发生。您可能已经预料到这种替换失败不会是错误。然而,标准说:

14.8.2 模板参数推导[temp.deduct]第8条

如果替换导致无效的类型或表达式,则类型推导失败。无效类型或表达式是如果使用替换参数编写的格式错误的类型或表达式。只有函数类型及其模板参数类型的直接上下文中的无效类型和表达式会导致推导失败。[注意:替换类型和表达式的求值可能会导致副作用,例如类模板特化和/或函数模板特化的实例化,隐式定义函数的生成等。这些副作用不在“立即上下文”,并可能导致程序格式错误。——尾注]

在您的原始代码中,typename a_rt<T,T2>::type返回类型不是直接上下文。只有在模板实例化期间才会对其进行评估,然后缺少嵌套typert一个错误。

在您更新的代码A<typename rt<T,T2>::type>中,返回类型是即时上下文,并且适用替换失败不是错误(SFINAE):简单地从重载解决方案集中删除非推导函数模板,并调用剩余的函数模板。

使用您更新的代码,输出将是:

> A<T2>& called     
> T2& called
于 2012-08-08T06:53:41.990 回答
0

您的前向声明使用class

template<typename T> class A;

但是您的定义使用struct

template <typename T>
struct A {

除此之外,我看不到任何问题...

于 2012-08-08T05:10:56.710 回答