【问题标题】:implicit conversion in template specialization模板特化中的隐式转换
【发布时间】:2019-04-29 09:05:44
【问题描述】:

在下面的代码中,我有一个非成员模板函数和它的完整特化类型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重载 来补充代码,而不是模板特化为

#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

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

【问题讨论】:

  • 您是否阅读了编译器错误信息?它说我像"template parameter 'U' is ambiguous"。所以问题不在于无法从char隐式转换为int,而是调用的歧义。
  • 来自this template argument deduction reference(更具体地说是implicit conversion section):“类型推导不考虑隐式转换......:这是重载解析的工作,稍后会发生。”
  • @vahancho 对不起,我使用和提到的编译器没有给出警告 ambigous
  • 另一个更喜欢重载而不是专业化的原因。

标签: c++ templates language-lawyer


【解决方案1】:

当编译器看到这个时:

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.

【讨论】:

    【解决方案2】:

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

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

    所以,当你有模板函数和模板特化,你写

    f(x, y);
    

    编译器推导出xy 的类型。

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

    当你打电话时

    f<someType>(x, y);
    

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

    在这种情况下,写作

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

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

    非模板函数是不同的,因为它永远可用,编译器只需验证所有参数是否相等或可转换为函数的参数。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-06
      相关资源
      最近更新 更多