Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implicit conversion of a variadic template class in a variadic function

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

like image 501
P. Rodríguez Avatar asked Nov 07 '22 10:11

P. Rodríguez


1 Answers

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

like image 188
Yakk - Adam Nevraumont Avatar answered Nov 15 '22 05:11

Yakk - Adam Nevraumont