模板专业化中的隐式转换

时间:2019-04-29 09:05:44

标签: 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’)

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

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

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

specialization int 1 1
generic a a
specialization int 97 1

如果相反,我用<{1}}的重载来补充代码,而不是模板专用化为

f

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

#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;
}

总结:为什么隐式转换对重载有效,但对看似等效的模板专门化却无效?

2 个答案:

答案 0 :(得分:4)

当编译器看到此内容时:

f('a', 1);

由于有两种选择,因此无法推断类型:

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

由于您的模板的两个参数的类型相同。

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

std::max会出现相同的问题。

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

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

答案 1 :(得分:1)

  

为什么隐式转换对重载有效,但对看似等效的模板专门化却无效?

因为模板特化正是一种特化。

因此,当您具有模板功能和模板专长时,您可以编写

f(x, y);

编译器推断出xy的类型。

当且仅当推导的类型相同时,请考虑模板函数,并且当且仅当类型为int(对于两个参数)时,选择特殊化。

致电时

f<someType>(x, y);

您说编译器:“忽略类型推导,并调用将f()强制为someType的模板函数T”。

在这种情况下,写作

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

编译器选择模板特化并将a转换为int

使用非模板函数的情况有所不同,因为它始终可用,并且编译器仅需验证所有参数是否等于或可转换为该函数的参数。

相关问题