Consider the following code
#include <functional>
template<class ResultType, class ... Args>
void Foo( std::function<ResultType(Args...)> ) {}
void Dummy(int) {}
int main()
{
Foo<void, int> ( std::function<void(int)>( Dummy ) ); // OK, no deduction and no conversion
Foo( std::function<void(int)>( Dummy ) ); // OK, template argument deduction
Foo<void, int>( Dummy ); // Compile error
}
In the third one I understand that a template deduction cannot take place, that's why the template argument is explicitly specified. But why there isn't an explicit conversion from void (*)(int)
to std::function<void(int)>
?
I looked up for answers but these are about ambiguous overloading resolution or template deductions, not the topic in question.
Isn't the template argument (the signature) of std::function part of its type?
Template type deduction with std::function
Implicit conversions with std::function
Then I tried to test with my own template class instead of std::function.
// Variadic template class
template<class ... T>
class Bar
{
public:
// Non-explicit ctor, an int can go through implicit conversion
Bar(int) {}
};
// A template function
template<class T>
void Xoo( Bar<T> ) {}
// Same, but this one has a variadic template
template<class ... T>
void Yoo( Bar<T...> ) {}
int main()
{
Xoo( Bar<bool>( 100 ) ); //OK, argument deduction
Xoo<bool>( 100 ); //OK, implicit conversion
Yoo( Bar<bool>( 100 ) ); //OK, argument deduction
Yoo<bool>( 100 ); // Not ok... ?
}
Output from GCC 9.2.0
prog.cc: In function 'int main()':
prog.cc:23:19: error: no matching function for call to 'Yoo<bool>(int)'
23 | Yoo<bool>( 100 ); // Not ok... ?
| ^
prog.cc:16:6: note: candidate: 'template<class ... T> void Yoo(Bar<T ...>)'
16 | void Yoo( Bar<T...> ) {}
| ^~~
prog.cc:16:6: note: template argument deduction/substitution failed:
prog.cc:23:19: note: mismatched types 'Bar<T ...>' and 'int'
23 | Yoo<bool>( 100 ); // Not ok... ?
| ^
Output from clang 9.0.0
prog.cc:23:4: error: no matching function for call to 'Yoo'
Yoo<bool>( 100 ); // Not ok... ?
^~~~~~~~~
prog.cc:16:6: note: candidate template ignored: could not match 'Bar<bool, type-parameter-0-0...>' against 'int'
void Yoo( Bar<T...> ) {}
^
1 error generated.
Why, if the function has variadic template, implicit conversion doesn't take place (even when the template arguments are explicitly specified)? I went back to std::function and, sure enough, if the function doesn't have a variadic template, it works.
#include <functional>
// Not variadic this time
template<class ResultType, class Arg>
void Goo( std::function<ResultType(Arg)> ) {}
void Dummy(int) {}
int main()
{
Goo<void, int> ( Dummy ); // Ok this time
}
Interestingly, the following modification makes it compile in clang
[...]
// Same, but this one has a variadic template
template<class ... T>
void Yoo( Bar<T..., bool> ) {}
// ^^^^
// An extra template for Bar makes implicit conversion
// work for some reason
[...]
I tried looking for more answers related to variadic templates but there are either not about this specific topic or too advance for me to understand at this point.
How to overload variadic templates when they're not the last argument
Template parameter pack deduction when not passed as last parameter
Deduction guides and variadic class templates with variadic template constructors - mismatched argument pack lengths
Template argument and deduction of std::function parameters
Deduction guides and variadic class templates with variadic template constructors - mismatched argument pack lengths
template<class ResultType, class ... Args>
void Foo( std::function<ResultType(Args...)> ) {}
Foo<void, int> ( std::function<void(int)>( Dummy ) ); // OK, no deduction and no conversion
Foo( std::function<void(int)>( Dummy ) ); // OK, template argument deduction
Foo<void, int>( Dummy ); // Compile error
Foo<void,int>
doesn't do what you think it does.
You think it explicitly specifies the template arguments to Foo
. What it actually does is state that the template arguments for Foo
start with void
then an int
, and ... then it says nothing.
So template argument deduction still runs to find out what the rest of the arguments are. It fails. Then your compiler complains.
To see this
Foo<void, int> ( std::function<void(int,int)>( nullptr) );
you'll see we passed in void,int
, but what was deduced was void,int,int
-- two int
s not one.
...
For your particular problem, you are mixing template argument deduction with a type erasure type (std::function
), and doing them both is a lot like painting a car in because you want to peel the paint off.
The operations of template argument deduction and type erasure are imperfect inverses of each other.
When there is a variartic pack left, there is no deduction to do once you pass every argument, so it no longer does template argument deduction.
In the case of your Bar<T...,bool>
you are blocking argument deduction because C++ refuses to deduce packs when there is anything after them.
If you really want this, you can do:
template<class T>
struct identity { using type=T; };
template<class T> using identity_t = typename identity<T>::type;
template<class ResultType, class ... Args>
void Foo( identity_t<std::function<ResultType(Args...)>> ) {}
which also blocks template argument deduction.
Now
Foo( std::function<void(int)>( Dummy ) ); // OK, template argument deduction
doesn't work, as it refuses to deduce the argument.
You can support both with a bit of tomfoolery:
template<class ResultType, class ... Args>
void Foo( identity_t<std::function<ResultType(Args...)>> ) {}
struct never_use {};
template<class R0=never_use, class ResultType, class ... Args>
requires (std::is_same_v<R0, never_use>)
void Foo( std::function<ResultType(Args...)> ) {}
but this doesn't support passing partial arguments without even more tomfoolery.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With