Search code examples
c++template-meta-programmingoverload-resolutionfunction-templates

c++ address of an overloaded function


I have the following small C++ code sample, but I can't figure out why the compiler works this way, although I have spent quite a lot of time studying cppreference. I would appreciate any explanations! godbolt

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

Solution

  • void (*p)(int) = func; does overload resolution similarly to func(1), with one crucial difference.

    Both the call and the assignment will first lookup func and find the two function templates. Template argument deduction will be done on both templates Tp = int will be deduced for both of them.

    Then Tp will be substituted into the templates. This is why your static_assert(depend_type<Tp>::false_); will fire because the compiler needs to figure out what typename cont<Tp>::x is in the return type of the second function.

    Then the best viable function needs to be found. For overload resolution in the call (func(1)), neither overload is better than the other (both take an argument int that came from Tp). There is nothing else to compare these two overloads, so it is ambiguous.

    But in the assignment case (void (*p)(int) = func;), the return type is also considered. In the first function template, void didn't come from a template, so it is better than the second function template, where the void is a dependent type. So the first function template is chosen.

    This might be easier to understand with a different example:

    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
    }
    

    This is one of three scenarios where function return type is considered during overload resolution. The second being type conversion operator return types and the third during explicit (full) specialization.