Quentin
Quentin

Reputation: 63144

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;

Upvotes: 6

Views: 355

Answers (1)

Passer By
Passer By

Reputation: 21160

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

Upvotes: 1

Related Questions