c++ - 模板特化中的隐式转换

标签 c++ templates language-lawyer

在下面的代码中,我有一个非成员模板函数和一个针对 int 类型的完全特化。

#include <iostream>

template <typename U>
void f(const U& x, const U& y)
{
  std::cout << "generic " << x << " " << y << std::endl;
}

template <>
void f(const int& x, const int& y)
{
  std::cout << "specialization int " << x << " " << y << std::endl;
}

int main()
{
  int a = 1;
  f(a, a);
  f('a', 'a');

  f('a', 1); // Compiler error
  // f<int>('a', 1); // This compiles

  return 0;
}

虽然从 charint 的隐式转换在该语言中可用,但编译器(g++ 7.3.0 和 clang 6.0.1)不会编译代码,报错

error: no matching function for call to ‘f(char, int)’
deduced conflicting types for parameter ‘const U’ (‘char’ and ‘int’)

虽然很清楚为什么模板推导不起作用,但我不清楚为什么编译器在丢弃通用模板后不考虑隐式转换。例如,如果我使用 U=int 显式实例化 f 取消注释代码中的相应行作为

f<int>('a', 1);

然后代码编译并正确给出输出

specialization int 1 1
generic a a
specialization int 97 1

相反,如果我用 f重载 来补充代码,而不是模板特化 as

#include <iostream>

template <typename U>
void f(const U& x, const U& y)
{
  std::cout << "generic " << x << " " << y << std::endl;
}

void f(const int& x, const int& y)
{
    std::cout << "overload int " << x << " " << y << std::endl;
}

int main()
{
  int a = 1;
  f(a, a);
  f('a', 'a');

  f('a', 1);

  return 0;
}

然后代码编译并给出预期的输出

overload int 1 1
generic a a
overload int 97 1

总结:为什么隐式转换适用于重载但不适用于看似等效的模板特化?

最佳答案

当编译器看到这个时:

f('a', 1);

它无法推断类型,因为它有两个选择:

f(const char &, const char &);
f(const int &, const int &);

因为您的模板对两个参数都有共同的类型。

这两种选择都同样有效,没有合理的规则来解决这种歧义。因此编译器必须报告错误以避免意外行为。请注意,静默类型转换对此问题没有影响,而且您对模板的特化也无助于解决此问题。

std::max 也会出现同样的问题。

现在的问题是,您确定第二个参数更重要并且应该对模板参数类型产生影响吗?如果是,那么您可以强制忽略第一个参数的类型(免责声明:这是不寻常且意外的,因此它可能是 future 代码维护者容易出现的错误)。

template <typename T>
struct Identity {
    using type = T;
};
// note C++20 introduces std::type_identity

template<typename T>
void f(const typename Identity<T>::type& x, const T& y)
{
  std::cout << "generic " << x << " " << y << std::endl;
}

在这种形式中,第一个参数不会参与模板的类型推导,因为它取决于第二个参数的类型。

现在这个

f('a', 1);

将编译,因为第二个参数将导致 T=int 并且第一个参数类型应与第二个参数相同。现在可以执行静默转换。

Live example .

关于c++ - 模板特化中的隐式转换,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55900098/

相关文章:

c++ - 无法从派生类型的范围访问另一个实例的 protected 成员

c++ - 错误 : 'a pointer to member is not valid for a managed class'

c++ - 类型转换模板

c++ - 当我在某些情况下使用模板参数时,编译器如何生成函数实例?

c++ - 策略类设计但没有将整个用户类作为模板

c++ - 是否保证容器会在 std::move 期间破坏现有对象?

c++ - std::less 枚举

c++ - 如何使用OpenCV在不同的显示器上显示不同的窗口

c++ - Mingw 容易混淆的函数名

c++ - 在OpenCV C++中播放视频文件