模板专业化中的隐式转换

fra*_*sco 1 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;
}
Run Code Online (Sandbox Code Playgroud)

尽管该语言提供了从a char到a 的隐式转换int,但是编译器(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’)
Run Code Online (Sandbox Code Playgroud)

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

f<int>('a', 1);
Run Code Online (Sandbox Code Playgroud)

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

specialization int 1 1
generic a a
specialization int 97 1
Run Code Online (Sandbox Code Playgroud)

相反,如果我用重载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;
}
Run Code Online (Sandbox Code Playgroud)

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

overload int 1 1
generic a a
overload int 97 1
Run Code Online (Sandbox Code Playgroud)

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

Mar*_*k R 5

When compiler see this:

f('a', 1);
Run Code Online (Sandbox Code Playgroud)

It is unable to deduce type since it has two choices:

f(const char &, const char &);
f(const int &, const int &);
Run Code Online (Sandbox Code Playgroud)

Since your template has common type for both arguments.

Both choices are equally valid and there is no reasonable rule to resolve this ambiguity. So compiler has to report an error to avoid undesired behavior. Note that silent type conversion has no impact on this problem, also your specialization of template also can't help resolve this issue.

Same problem will be raised for std::max.

Now question is are you sure that second argument is more important and should have impact on template argument type? If yes then you can force to ignore type of first argument (disclaimer: this is unusual and unexpected so it may be a bug prone for future code mainteinrs).

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

Run Code Online (Sandbox Code Playgroud)

In this form first argument will not take a part in type deduction for template, since it depends on type of second argument.

Now this

f('a', 1);
Run Code Online (Sandbox Code Playgroud)

will compile since second argument will lead to T=int and first argument type is expected to be same as for second argument. Now silent conversion can be performed.

Live example.