【问题标题】:decltype, recursive type deduction for overloaded operatordecltype,重载运算符的递归类型推导
【发布时间】:2015-08-30 00:46:53
【问题描述】:

对于一个带有表达式模板的类,我在重载运算符的返回类型推导过程中偶然发现了以下错误。下面的例子说明了错误:

template < typename T >
struct A_Number {
     T x;
};


// TAG1
template < typename T >
A_Number< T >
operator+(const A_Number< T > &a, const A_Number< T > &b)
{
    return {a.x + b.x};
}

// TAG2
template < typename T, typename S >
A_Number< T >
operator+(const A_Number< T > &a, const S &b)
{
    return {a.x + b};
}

// TAG3
template < typename T, typename S >
auto
operator+(const S &b, const A_Number< T > &a) -> decltype(a + b)
//                                                        ^^^^^
{
    return a + b;
}

int
main(void)
{
    auto x1 = A_Number< int >{1};
    auto x2 = A_Number< int >{1};

    auto res1 = x1 + 1;  // instantiates TAG2

    auto res2 = 1 + x1;  // instantiates TAG3, TAG2

    auto res3 = x1 + x2; // error, tries to match TAG3
    return EXIT_SUCCESS;
}

当尝试用 g++-5 或 clang++ 编译时,我得到了这个错误

fatal error: template instantiation depth exceeds maximum of 900 (use -ftemplate-depth= to increase the maximum)
 operator+(const S &b, const A_Number< T > &a) -> decltype(a + b)

显然,编译器尝试匹配版本 TAG3,尽管有更好的匹配 (TAG1)。在尝试匹配时,他们尝试推断返回类型,这似乎会导致 TAG3 的递归实例化。为什么返回类型推导看不到其他(更好匹配的)重载?即使另一个重载的模板函数具有更好的匹配签名,推断返回类型是否正确?

有趣的是,当完全省略返回类型并使用 c++14 编译时,这个错误会凭空消失,如下所示:

// TAG3
template < typename T, typename S >
auto
operator+(const S &b, const A_Number< T > &a) // C++14
{
    return a + b;
}

诚然,这是一个学术问题,因为解决方法是可能的。但是任何人都可以阐明这种行为是符合标准还是编译器错误?

【问题讨论】:

    标签: c++ templates c++11 language-lawyer decltype


    【解决方案1】:

    这实际上是编译器部分的正确行为。重载解析有两个步骤,从[over.match]:

    ——首先,候选函数的一个子集(那些具有适当数量的参数并且满足 某些其他条件)被选择以形成一组可行的功能(13.3.2)。
    — 然后根据所需的隐式转换序列(13.3.3.1)选择最佳可行函数 将每个参数与每个可行函数的相应参数匹配。

    x1 + x2 的候选函数是:

    // TAG1
    template < typename T >
    A_Number< T >
    operator+(const A_Number< T > &a, const A_Number< T > &b);
    
    // TAG2
    template < typename T, typename S >
    A_Number< T >
    operator+(const A_Number< T > &a, const S &b);
    
    // TAG3
    template < typename T, typename S >
    auto operator+(const S &b, const A_Number< T > &a) -> decltype(a + b)
    

    好吧,我们需要先确定TAG3 的返回类型是什么,然后再选择最佳可行的候选者。现在,operator+ 在这里是一个依赖名称,因为它依赖于两个模板参数。所以根据[temp.dep.res]:

    在解析从属名称时,会考虑来自以下来源的名称:
    — 在模板定义处可见的声明。
    — 来自与函数参数类型相关的命名空间的声明 实例化上下文 (14.6.4.1) 和定义上下文。

    所以要解析TAG3 的返回类型,我们需要对a+b 进行查找和重载解析。这又给了我们同样的三个候选者(前两个找到了通常的方式,TAG3 not-so-helpfully 再次通过 ADL 找到),所以我们绕了一圈又一圈。

    您的解决方案是:

    • 在 C++14 中,删除 TAG3 的尾随返回类型。这将停止旋转木马,我们将从三个可行的候选人开始,其中TAG1 最专业,因此将被选为最佳可行候选人。
    • 如果SA_Number&lt;T&gt;,则防止TAG3 被实例化。例如,我们可以为A_Number 创建一个类型特征:

      template <typename T>
      struct is_A_Number : std::false_type { };
      
      template <typename T>
      struct is_A_Number<A_Number<T>> : std::true_type { };
      
      // TAG3
      template < typename T, typename S, 
                 typename = typename std::enable_if<!is_A_Number<S>::value>::type>
      auto operator+(const S &b, const A_Number< T > &a) -> decltype(a + b);
      

    即使在 C++11 中也可以编译。

    【讨论】:

      猜你喜欢
      • 2013-03-15
      • 2011-01-22
      • 1970-01-01
      • 2021-03-05
      • 2020-05-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多