模板特化中的隐式转换

3

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

Original Answer翻译成:"最初的回答"

#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的重载而不是模板特化来补充代码,则为“最初的回答”。
#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

总结一下:为什么隐式转换可以适用于重载,但似乎等效的模板特化却不行?

最初的回答:

模板特化是一种更加严格和精确的类型匹配,因此它不允许任何类型转换。而重载则可以使用隐式转换,因为它们可以处理其他类型的参数。


你读了编译器的错误信息吗?它告诉我类似于“模板参数 'U' 不明确”。所以问题不在于无法从 char 隐式转换为 int,而是调用的不明确。 - undefined
2
从这个模板参数推导参考文档(具体地说是隐式转换部分)中可以得出:类型推导不考虑隐式转换……这是后续重载决议的工作。 - undefined
@vahancho 抱歉,我使用和提到的编译器没有给出模棱两可的警告。 - undefined
更喜欢重载而不是特化的另一个原因。 - undefined
2个回答

6
编译器看到以下代码时:

当编译器看到这行代码:

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,第一个参数类型应与第二个参数相同。现在可以进行静默转换。

实时示例


1
为什么隐式转换适用于重载,但不适用于看似等效的模板特化?
因为模板特化是一个特化。
因此,当您拥有模板函数和模板特化,并编写时,隐式转换可能适用于函数重载,但不适用于模板特化。
f(x, y);

编译器推断出xy的类型。仅当推断出的类型相同时,考虑模板函数;仅当类型为int(对于两个参数)时,选择特化。
当您调用时:
f<someType>(x, y);

你需要告诉编译器:“忽略类型推导并调用模板函数 f(),将 someType 作为 T”。请注意保留HTML标签。
在这种情况下,写成:
f<int>('a', 1);

编译器选择模板特化并将a转换为int
与非模板函数不同,因为它始终可用,编译器只需验证所有参数是否等于或可转换为函数的参数。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接