首页 > 解决方案 > C++ 模板选择 - 不寻常的情况

问题描述

我有以下定义

  template <typename T1, typename T2> class ArithmeticType {
  public:
    T1 x1;
    T2 x2;
    typedef typeof(x1+x2) Type;
  };

  template <typename D1, typename D2> inline
    std::complex<typename ArithmeticType<D1,D2>::Type>
    operator+(const std::complex<D1>& x1, const std::complex<D2>& x2) {
    D1 x1r = real(x1);
    D1 x1i = imag(x1);
    D2 x2r = real(x2);
    D2 x2i = imag(x2);
    return std::complex<typename ArithmeticType<D1,D2>::Type>(x1r+x2r, x1i+x2i);
  }

然后我使用这个类如下

    std::complex<double> x;
    std::cout << typeid(x).name() << std::endl;
    ArithmeticType<std::complex<double>,std::complex<float>>::Type y;
    std::cout << typeid(y).name() << std::endl;

产生输出的

St7complexIdE
St7complexIdE

这对我来说很有意义: y是类型std::complex<double>

现在,随着添加复杂和浮动类型的缩进,我添加了operator+模板的特化,因此代码变为

  template <typename T1, typename T2> class ArithmeticType {
  public:
    T1 x1;
    T2 x2;
    typedef typeof(x1+x2) Type;
  };

  template <typename D1, typename D2> inline
    std::complex<typename ArithmeticType<D1,D2>::Type>
    operator+(const std::complex<D1>& x1, const std::complex<D2>& x2) {
    D1 x1r = real(x1);
    D1 x1i = imag(x1);
    D2 x2r = real(x2);
    D2 x2i = imag(x2);
    return std::complex<typename ArithmeticType<D1,D2>::Type>(x1r+x2r, x1i+x2i);
  }

  template <typename D1, typename D2> inline
    std::complex<typename ArithmeticType<D1,D2>::Type>
    operator+(const std::complex<D1>& x1, const D2 x2) {
    D1 x1r = real(x1);
    D1 x1i = imag(x1);
    return std::complex<typename ArithmeticType<D1,D2>::Type>(x1r+x2, x1i+x2);
  }

我运行与以前相同的代码,但现在出现编译错误,因为编译器尝试使用第二个特化而不是第一个。这对我来说没有意义。我认为 (g++) 编译器仍然会选择第一个专业化。

有人可以解释为什么会发生这种情况的规则吗?

标签: c++templates

解决方案


我相信您正在观察cppreference/SFINAE的这句话引起的行为:

只有函数类型或其模板参数类型的直接上下文中的类型和表达式的失败才是 SFINAE 错误。如果替换类型/表达式的评估导致副作用,例如某些模板特化的实例化、隐式定义的成员函数的生成等,则这些副作用中的错误被视为硬错误

一个最小的例子:

template <typename T>
struct has_foo
{
   using foo_t = decltype(T{}.foo());
};

template <typename T> void f(T&&) { }

// SFINAE error => no compilation error:
template <typename T> decltype(T{}.foo()) f(T&&) { }

// non-SFINAE error => hard error:
template <typename T> typename has_foo<T>::foo_t f(T&&) { }

int main()
{
   f(1);
}

您的代码基本上表现相同,只是has_foo您没有ArithmeticType.


至于使用 的返回类型推导auto,请参见例如:SFINAE with C++14 return type deduction

与上述相同的工作示例has_foo

#ifndef DEFINE_THIS_SYMBOL_TO_CAUSE_COMPILATION_ERROR
void f(int) { }
#endif

// no SFINAE => viable candidate
template <typename T> auto f(T&&)
{
   return typename has_foo<T>::foo_t{};
}

int main()
{
   f(1);
}

如果void f(int) { }已定义,则它的匹配性更好,并且f不会实例化模板。


推荐阅读