Disambiguating an overloaded function with a given pack of parameters - c++

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;

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

Related

Misunderstanding variadic templates and template type deduction

I'm compiling with C++17 with code similar to this sample:
#include <iostream>
#include <iterator>
class Foo {};
template <typename... Args,
typename ostream_type = ::std::basic_ostream<Args...>,
typename ostreambuf_iterator_type = ::std::ostreambuf_iterator<Args...>>
ostream_type &operator<<(ostream_type &os, const ::Foo f) {
// Do ostreambuf_iterator_type stuff with 'f' here...
return os;
}
int main() {
::Foo f;
::std::cout << f;
return 0;
}
What I've discovered is template type deduction fails when I apply Args... to the template parameter list of both ostream_type and ostreambuf_iterator_type, but it would be fine if I assigned the char_type and the traits_type from the ostream_type.
typename ostreambuf_iterator_type = ::std::ostreambuf_iterator<typename ostream_type::char_type, typename ostream_type::traits_type>>
Why is this, when the template parameters to ::std::basic_ostream and ::std::ostreambuf_iterator are the same?
Template argument deduction tries to deduce ostream_type from the function argument. In doing so it is not bound by the default argument you provided. Rather the default argument is simply ignored.
ostream_type will be deduced to std::basic_ostream<char>.
Then there is nothing left depending on Args in the function parameters and the parameter pack is deduced to be empty.
The empty Args is then expanded into the default argument ostreambuf_iterator_type = ::std::ostreambuf_iterator<Args...>, which fails because std::ostreambuf_iterator needs at least one template argument.
If you want Args to be deduced as the template arguments to the std::basic_ofstream passed to your function, you need to constraint template argument deduction in the parameter:
template <typename... Args,
typename ostreambuf_iterator_type = ::std::ostreambuf_iterator<Args...>>
auto &operator<<(::std::basic_ostream<Args...> &os, const Foo f) {
// Do ostreambuf_iterator_type stuff with 'f' here...
return os;
}
Now the deduction has to deduce Args as the template arguments of os.

template argument deduction for class templates not working with gcc 8.1.0? [duplicate]

Consider the following code:
#include <tuple>
#include <iostream>
template <class T>
struct custom_wrapper
{
template <class Arg>
custom_wrapper(Arg arg): data(arg) {}
T data;
};
template <class Arg>
custom_wrapper(Arg arg) -> custom_wrapper<Arg>;
template <class... T>
struct custom_tuple
{
template <class... Args>
custom_tuple(Args... args): data(args...) {}
std::tuple<T...> data;
};
template <class... Args>
custom_tuple(Args... args) -> custom_tuple<Args...>;
int main(int argc, char* argv[])
{
custom_wrapper<int> w1(42); // OK
custom_wrapper w2(42); // OK
custom_tuple<int> t1(42); // OK
custom_tuple t2(42); // Fails
return 0;
}
The line that fails return the following error under g++7:
variadic_deduction_guide.cpp: In instantiation of 'custom_tuple<T>::custom_tuple(Args ...) [with Args = {int}; T = {}]':
variadic_deduction_guide.cpp:31:23: required from here
variadic_deduction_guide.cpp:19:45: error: no matching function for call to 'std::tuple<>::tuple(int&)'
custom_tuple(Args... args): data(args...) {}
Is that normal or is that a compiler bug?
This is gcc bug 80871. What follows is an explanation of why the code is well-formed (and clang is correct in deciding that t2 is a custom_tuple<int>).
The process for figuring out what to do with
custom_tuple t2(42);
basically involves synthesizing a bunch of functions and performing overload resolution on them. The relevant candidates are the synthesized functions from the one constructor and the deduction guide:
template <class... T, class... Args>
custom_tuple<T...> foo(Args... ); // the constructor
template <class... Args>
custom_tuple<Args...> foo(Args... ); // the deduction guide
From this point it's a choose your own adventure based on your interpretation of what a "trailing parameter pack" is according to [temp.arg.explicit]/3:
A trailing template parameter pack not otherwise deduced will be deduced to an empty sequence of template arguments. If all of the template arguments can be deduced, they may all be omitted; in this case, the empty template argument list <> itself may also be omitted.
T... isn't trailing
This case is easy. We only have one viable candidate (because T... isn't deducible) - the deduction-guide candidate. We deduce Args... as {int}, so we end up with custom_tuple<int>.
T... is trailing
Both gcc and clang actually do consider deduction to succeed for the constructor. So we go to the tiebreakers in [over.match.best]:
Given these definitions, a viable function F1 is defined to be a better function than another viable function F2 if [...]
F1 and F2 are function template specializations, and the function template for F1 is more specialized than the template for F2 according to the partial ordering rules described in [temp.func.order], or, if not that,
F1 is generated from a deduction-guide ([over.match.class.deduct]) and F2 is not, or, if not that, [...]
For purposes of partial ordering, the relevant types are just those which correspond to function parameters, and we're allowed to ignore unused template parameters, so neither function template is considered more specialized than the other.
This leaves us to simply preferring the deduction-guide, which has been the simplest step of this whole process. We deduce Args... as {int}, so we end up with custom_tuple<int>.
Either way, custom_tuple<int> is the correct decision.

Deduction guide and variadic templates

Consider the following code:
#include <tuple>
#include <iostream>
template <class T>
struct custom_wrapper
{
template <class Arg>
custom_wrapper(Arg arg): data(arg) {}
T data;
};
template <class Arg>
custom_wrapper(Arg arg) -> custom_wrapper<Arg>;
template <class... T>
struct custom_tuple
{
template <class... Args>
custom_tuple(Args... args): data(args...) {}
std::tuple<T...> data;
};
template <class... Args>
custom_tuple(Args... args) -> custom_tuple<Args...>;
int main(int argc, char* argv[])
{
custom_wrapper<int> w1(42); // OK
custom_wrapper w2(42); // OK
custom_tuple<int> t1(42); // OK
custom_tuple t2(42); // Fails
return 0;
}
The line that fails return the following error under g++7:
variadic_deduction_guide.cpp: In instantiation of 'custom_tuple<T>::custom_tuple(Args ...) [with Args = {int}; T = {}]':
variadic_deduction_guide.cpp:31:23: required from here
variadic_deduction_guide.cpp:19:45: error: no matching function for call to 'std::tuple<>::tuple(int&)'
custom_tuple(Args... args): data(args...) {}
Is that normal or is that a compiler bug?
This is gcc bug 80871. What follows is an explanation of why the code is well-formed (and clang is correct in deciding that t2 is a custom_tuple<int>).
The process for figuring out what to do with
custom_tuple t2(42);
basically involves synthesizing a bunch of functions and performing overload resolution on them. The relevant candidates are the synthesized functions from the one constructor and the deduction guide:
template <class... T, class... Args>
custom_tuple<T...> foo(Args... ); // the constructor
template <class... Args>
custom_tuple<Args...> foo(Args... ); // the deduction guide
From this point it's a choose your own adventure based on your interpretation of what a "trailing parameter pack" is according to [temp.arg.explicit]/3:
A trailing template parameter pack not otherwise deduced will be deduced to an empty sequence of template arguments. If all of the template arguments can be deduced, they may all be omitted; in this case, the empty template argument list <> itself may also be omitted.
T... isn't trailing
This case is easy. We only have one viable candidate (because T... isn't deducible) - the deduction-guide candidate. We deduce Args... as {int}, so we end up with custom_tuple<int>.
T... is trailing
Both gcc and clang actually do consider deduction to succeed for the constructor. So we go to the tiebreakers in [over.match.best]:
Given these definitions, a viable function F1 is defined to be a better function than another viable function F2 if [...]
F1 and F2 are function template specializations, and the function template for F1 is more specialized than the template for F2 according to the partial ordering rules described in [temp.func.order], or, if not that,
F1 is generated from a deduction-guide ([over.match.class.deduct]) and F2 is not, or, if not that, [...]
For purposes of partial ordering, the relevant types are just those which correspond to function parameters, and we're allowed to ignore unused template parameters, so neither function template is considered more specialized than the other.
This leaves us to simply preferring the deduction-guide, which has been the simplest step of this whole process. We deduce Args... as {int}, so we end up with custom_tuple<int>.
Either way, custom_tuple<int> is the correct decision.

Function template parameter pack not at the end of the parameter list

The following code compiles and runs ok.
void foo() {
}
template <typename T, typename... Args>
void foo(T x, Args... args) {
cout << x << endl;
foo(args...);
}
// inside main()
foo(1,1,1);
This other code does not compile:
void foo() {
}
template <typename... Args, typename T>
void foo(Args... args, T x) {
foo(args...);
cout << x << endl;
}
// inside main()
foo(1,1,1);
The compiler says that there is no matching function for call to foo(1,1,1) and says that foo(Args... args, T x) is a candidate, but template argument deduction/substitution failed, because candidate expects 1 argument, but 3 were provided.
Is there any ambiguity with this situation that no compiler can handle? This compile error just seems illogical to me. Maybe this is not in accordance, on purpose, with the C++ standard?
(This answer is based on #JohannesSchaub-litb's comments)
According to the standard, template parameter pack is not deducible if it is used in a function parameter pack not at the end of the parameter list.
§14.8.2.1/1 Deducing template arguments from a function call
[temp.deduct.call]:
When a function parameter pack appears in a non-deduced context
([temp.deduct.type]), the type of that parameter pack is never
deduced. [ Example:
template<class T1, class ... Types> void g1(Types ..., T1);
void h(int x, float& y) {
const int z = x;
g1(x, y, z); // error: Types is not deduced
g1<int, int, int>(x, y, z); // OK, no deduction occurs
}
— end example ]
And about non-deduced context, §14.8.2.5/5 Deducing template arguments from a type
[temp.deduct.type]:
A function parameter pack that does not occur at the end of the parameter-declaration-list.
So the direct reason of foo(1,1,1); failed is that the template parameter Args is not deduced, which is necessary to make the function invocation valid.
To explain the error message, a template parameter pack not deduced will be deduced to an empty sequence of template arguments[1], it means it'll be omitted. Then foo(1,1,1); failed because the number of arguments doesn't match, that's what compiler complained.
Just as the example from standard shown, you could specify the template argument explicitly to avoid type deduction, even though it doesn't meet the original intent of your code. Such as:
template <typename T, typename... Args>
void foo(Args... args, T x) {
}
int main() {
// inside main()
foo<int, int, int>(1, 1, 1);
}
Here're some additional informations.
[1] I can't find direct expression about this in the standard. The most close one is this, "A trailing template parameter pack ([temp.variadic]) not otherwise deduced will be deduced to an empty sequence of template arguments."
The interesting part from Clang's error message is:
main.cpp:11:6: note: candidate template ignored: couldn't infer template argument 'T'
void foo(Args... args, T x) {
^
The problem is that the parameter pack Args... occurs prior to T.
Args... is "greedy" and so no parameters are left for the compiler to deduce T, hence it fails.
Quoting the standard (emphasis mine):
[temp.param]/11
A template parameter pack of a function template shall not be followed
by another template parameter unless that template parameter can be
deduced from the parameter-type-list of the function template or has a
default argument. [Example:
...
// U can be neither deduced from the parameter-type-list nor specified
template<class... T, class... U> void f() { } // error
template<class... T, class U> void g() { } // error
— end example]

Why is this substitution of variadic template parameter failing ? (pack before fixed arguments)

Here is a minimal example triggering the compilation error:
#include <utility>
void foo(int, double, int)
{}
template <class... Args>
void post_forwarder(void(*fun)(Args..., int), Args&&... aArgs)
{
fun(std::forward<Args>(aArgs)..., 5);
}
int main()
{
post_forwarder(foo, 6, 6.1); // Compilation error on instantiation
return 0;
}
I suspect the problem is related to the fact that the variadic template parameter is expanded in the function type before the fixed int parameter, but if it is the case I cannot find a good rationale for it.
The error reported by Clang 3.6 is:
error: no matching function for call to 'post_forwarder'
note: candidate template ignored: failed template argument deduction
Argument deduction fails here:
template <class... Args>
void post_forwarder(void(*fun)(Args..., int), Args&&... aArgs)
// ^^^^^^^
for the general rule that parameter packs have to be at the end to be deducible. The usual solution is to wrap it in a non-deducible context, so that deduction isn't even attempted:
template <typename T>
struct identity {
using type = T;
};
template <class... Args>
void post_forwarder(void(*fun)(typename identity<Args>::type..., int), Args&&... aArgs)
{
fun(std::forward<Args>(aArgs)..., 5);
}
This works:
template <class F, class... Args>
void post_forwarder(F f, Args&&... aArgs) {
f(std::forward<Args>(aArgs)..., 5);
}
LIVE DEMO
Edit: Rewritten answer:
The form Args..., int doesn't allow to deduce Args....