I have a class with multiple constructors, one being variadic and one taking an additional callable before the variadic arguments. However, the objects I create result in overload resolutions I dont understand. Here's my snippet:
#include <iostream>
#include <functional>
struct Test
{
std::function<void()> func;
template<class... Args>
Test(Args&&... args)
{
std::cout << "Variadic" << std::endl;
}
template<class... Args>
Test(decltype(func) newfunc, Args&&... args)
{
std::cout << "Variadic with a callable" << std::endl;
}
};
int main()
{
auto l = [](){};
Test t{1,2,3}; //As expected
Test t2{l, 1,2,3}; //Not as expected
Test t3{{l}, 1,2,3}; //As expected, but why???
//Same test, just with temporary
Test t4{[](){}, 1,2,3};
Test t5{{[](){}}, 1,2,3};
return 0;
}
The output from this program (Both gcc and MSVC) is
Variadic
Variadic
Variadic with a callable
Variadic
Variadic with a callable
The first call makes perfect sense, but I would expect call 2 to result in the one taking a callable since std::function objects can be created from lambdas. However, it doesnt, but as soon as I wrap that lambda in another pair of braces in case 3, essentially converting it to an initializer_list with 1 element, the correct constructor is invoked. What I dont understand is why case 2 does not result in the 2nd overload being chosen and why making it an initializer list changes this behavior.
a lambda is not std::function
, it is convertible_to one.
when the compiler sees a variadic function of the form
template<class... Args>
Test(Args&&... args)
...
template<class... Args>
Test(std::function<void()> newfunc, Args&&... args)
...
Test t4{[](){}, 1,2,3};
the first one is a perfect fit, it doesn't require any cast, the second one requires a cast from a lambda to std::function
, so the first one is chosen by overload resolution.
in the third case,
Test t3{{l}, 1,2,3};
the braces {l}
are calling a an implicit conversion then passing the result to the function, so the compiler has to find an overload that requires an implicit conversion, so it decides to call the std::function
constructor, then do ADL, which results in the second function to be the perfect fit because it is more specialized than function 1 and no cast is required, the first one doesn't require an implicit conversion so it doesn't participate in ADL.
refer to Implicit conversion sequence in list-initialization for how the compiler picks the best implicit conversion to do.
you can break this by applying a concept, or use enable_if with SFINAE and is_convertible
#include <iostream>
#include <functional>
#include <concepts>
template <typename T>
concept ConvertibleTofunc = std::convertible_to<T,std::function<void()>>;
struct Test
{
std::function<void()> func;
template<class... Args>
Test(Args&&... args)
{
std::cout << "Variadic" << std::endl;
}
template<ConvertibleTofunc Arg, class... Args>
Test(Arg&& newfunc, Args&&... args)
{
std::cout << "Variadic with a callable" << std::endl;
}
};
int main()
{
auto l = [](){};
Test t{1,2,3};
Test t2{l, 1,2,3};
//Same test, just with temporary
Test t4{[](){}, 1,2,3};
return 0;
}
Variadic
Variadic with a callable
Variadic with a callable