Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Disambiguating an overloaded function with a given pack of parameters

I am trying to implement a function template ovl such that ovl<Foo, Bar>(f) will return the overload of f taking (Foo, Bar), and very surprised with what happens with my naïve solution:

template <class... Args, class Ret>
constexpr auto ovl(Ret (*const f)(std::type_identity_t<Args>...)) { return f; }

void foo();
void foo(int);
void foo(int, int);

int main() {
    ovl<int>(foo)(0);
}
prog.cc:26:5: fatal error: no matching function for call to 'ovl'
    ovl<int>(foo)(0);
    ^~~~~~~~
prog.cc:6:16: note: candidate template ignored: couldn't infer template argument 'Ret'
constexpr auto ovl(Ret (*const f)(std::type_identity_t<Args>...)) { return f; }
               ^

The same error appears with GCC and Clang. What's more, it actually works when enumerating possible arities myself:


template <class Ret>
constexpr auto ovl(Ret (*const f)()) { return f; }

template <class Arg0, class Ret>
constexpr auto ovl(Ret (*const f)(std::type_identity_t<Arg0>)) { return f; }

template <class Arg0, class Arg1, class Ret>
constexpr auto ovl(Ret (*const f)(std::type_identity_t<Arg0>, std::type_identity_t<Arg1>)) { return f; }

// ... ad nauseam.

Wandbox demo

Interestingly, keeping Args... but hardcoding the return type works as well:

template <class... Args>
constexpr auto ovl(void (*const f)(std::type_identity_t<Args>...)) { return f; }

It seems like partial explicit arguments are ignored when they are provided to a parameter pack, but why? And how can I ensure that they are considered when trying to disambiguate the function pointer?


Note: I have found the following workaround which bakes Args... first before deducing Ret, but am still interested in an answer as this is quite clunky.

template <class... Args>
struct ovl_t {
    template <class Ret>
    constexpr auto operator()(Ret (*const f)(Args...)) const { return f; }
};

template <class... Args>
constexpr ovl_t<Args...> ovl;
like image 858
Quentin Avatar asked Dec 08 '20 17:12

Quentin


People also ask

How do a function call gets matched to a set of overloaded functions?

When a function call is made to an overloaded function, the compiler steps through a sequence of rules to determine which (if any) of the overloaded functions is the best match. At each step, the compiler applies a bunch of different type conversions to the argument(s) in the function call.

What is function overloading with example in C++?

Function overloading is a feature of object-oriented programming where two or more functions can have the same name but different parameters. When a function name is overloaded with different jobs it is called Function Overloading.

What are the restrictions on overloaded function?

Function declarations that have equivalent parameter declarations. These declarations are not allowed because they would be declaring the same function. Function declarations with parameters that differ only by the use of typedef names that represent the same type.

What is “function overloading”?

In Function Overloading “Function” name should be the same and the arguments should be different. Function overloading can be considered as an example of polymorphism feature in C++.

What is the difference between function overloading and return types?

The return type of all these functions is the same but that need not be the case for function overloading. Note: In C++, many standard library functions are overloaded. For example, the sqrt () function can take double, float, int, etc. as parameters. This is possible because the sqrt () function is overloaded in C++.

What is the use of overloading on the basis of const type?

Overloading on the basis of const type can be useful when a function return reference or pointer. We can make one function const, that returns a const reference or const pointer, other non-const function, that returns non-const reference or pointer. See following for more details.

How to overload member methods in C++?

C++ allows member methods to be overloaded on the basis of const type. Overloading on the basis of const type can be useful when a function return reference or pointer.


1 Answers

I believe this is a compiler bug.

As @StoryTeller - Unslander Monica mentioned [temp.arg.explicit]

Template argument deduction can extend the sequence of template arguments corresponding to a template parameter pack, even when the sequence contains explicitly specified template arguments.

Which means even though we supplied an int argument, the compiler will attempt to deduce more arguments for Args.

However [temp.deduct.call]

If the argument is an overload set (not containing function templates), trial argument deduction is attempted using each of the members of the set. If deduction succeeds for only one of the overload set members, that member is used as the argument value for the deduction. If deduction succeeds for more than one member of the overload set the parameter is treated as a non-deduced context.

The trial deductions are

template<typename... Args, typename Ret>
void trial(Ret(*)(std::type_identity_t<Args>...));

trial<int>((void(*)())foo);         // fails
trial<int>((void(*)(int))foo);      // succeeds
trial<int>((void(*)(int, int))foo); // fails, trailing Args is non-deduced context

Implying that only the void(int) member is used as the argument value, which will succeed in deducing Ret = void and Args = {int}.

like image 71
Passer By Avatar answered Oct 19 '22 08:10

Passer By