C++ 重载函数的地址

Евг*_*цын 5 c++ function-templates overload-resolution template-meta-programming

我有以下小的 C++ 代码示例,但我无法弄清楚为什么编译器以这种方式工作,尽管我花了很多时间研究 cppreference。我将不胜感激任何解释!神箭

#include <type_traits>

template<typename Tp>
struct depend_type
{
  constexpr static bool false_ = false;
};

template<typename Tp>
struct cont
{
  using x = void;

  static_assert(std::is_same_v<Tp, int>);
  // if uncomment, will be complie error -> 'cont<Tp>::x' instantiated with 'void (*p)(int) = func;'
//   static_assert(depend_type<Tp>::false_); 
};

template<typename Tp>
void func(Tp)
{
}

template<typename Tp>
typename cont<Tp>::x func(Tp);

int main(int /* argc */, char * /*argv*/[])
{
//  func(1); is ambiguous call
  void (*p)(int) = func; // why is not ambiguous?
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

Art*_*yer 7

void (*p)(int) = func;重载解析与 类似func(1),但有一个关键区别。

调用和赋值都会首先查找func并找到两个函数模板。模板参数推导将在两个模板上进行,Tp = int并对它们进行推导。

然后Tp将被替换到模板中。这就是为什么你static_assert(depend_type<Tp>::false_);会触发,因为编译器需要弄清楚typename cont<Tp>::x第二个函数的返回类型是什么。

然后需要找到最佳可行的函数。对于调用 ( func(1)) 中的重载决策,两个重载都不比另一个重载更好(两者都采用int来自 的参数Tp)。没有其他东西可以比较这两个重载,因此它是不明确的。

但在赋值情况(void (*p)(int) = func;)中,还需要考虑返回类型。在第一个函数模板中,void不是来自模板,因此它比第二个函数模板更好,其中void是依赖类型。所以选择第一个函数模板。

用一个不同的例子可能会更容易理解:

template<class T>
T** f() {
    std::cout << "T**\n";
    return nullptr;
}
template<class T>
T* f() {
    std::cout << "T*\n";
    return nullptr;
}
template<class T>
T f() {
    std::cout << "T\n";
    return T{};
}

int main() {
    // void** p = f<void>();  // Doesn't compile: return type doesn't participate in ranking most viable function

    // void** is a better match for T** than T* or T (it's more specialized)
    static_cast<void**(&)()>(f)();  // T**

    // void* is a better match for T* than T
    static_cast<void*(&)()>(f)();  // T*

    // T = void for the third function template is the only option
    static_cast<void(&)()>(f)();  // T
}
Run Code Online (Sandbox Code Playgroud)

这是在重载决策期间考虑函数返回类型的三种情况之一。第二个是类型转换运算符返回类型,第三个是显式(完全)专业化期间的类型。