Why is the converting constructor of std::packaged_task
explicit
, while the same constructor of std::function
is not? I cannot find any reasoning for it.
This, for example, forces casting when passing a lambda as an argument for a function that has a packaged_task
(or a reference to it) as a parameter:
void f1(std::function<void()>);
std::future<void> f2(std::packaged_task<void()>);
int main()
{
f1( []{ } ); // ok
auto fut = f2( []{ } ); // error
auto fut = f2( (std::packaged_task<void()>) []{ } ); // ok
fut.wait();
}
Consider following example. Lets create a template class that emulates class with non-explicit templated converting constructor.
#include <iostream>
// Hypothetical overloaded constructor
template <class T>
struct Foo {
template <class F>
Foo(F&& f ) { std::cout << "Initialization of Foo \n"; }
Foo(const Foo& ) { std::cout << "Copy of Foo\n"; }
Foo(Foo&& ) { std::cout << "Move of Foo\n"; }
};
void bar( Foo<int> f ) {}
int main()
{
int a = 0;
std::cout << "1st case\n";
bar(a);
std::cout << "2nd case\n";
bar(Foo<int>(a)); // incorrect behaviour
}
The output will be
1st case
Initialization of Foo
2nd case
Initialization of Foo
The template hijacks control in both cases! You actually cannot use copy\move constructors in such situation. A simplest way to avoid it is to make conversion explicit.
#include <iostream>
// Analog of standard's constructor idiom
template <class T>
struct Foo2 {
template <class F>
explicit Foo2(F&& f ) { std::cout << "Initialization of Foo2 \n"; }
Foo2(const Foo2& ) { std::cout << "Copy of Foo2\n"; }
Foo2(Foo2&& ) { std::cout << "Move of Foo2\n"; }
};
void bar2( Foo2<int> f ) {}
int main()
{
int a = 0;
Foo2<int> f{a};
std::cout << "\nProper case 1\n";
// bar2(a); - can't do that
bar2(Foo2<int>(a));
std::cout << "Proper case 2\n";
bar2(f);
return 0;
}
Output:
Initialization of Foo2
Proper case 1
Initialization of Foo2
Proper case 2
Copy of Foo2
std::function
is copyable while std::packaged_task
have to define custom move constructor and delete copying constructor.
On an attempt to pass std::function
, in both cases nothing BAD would happen, the copy and move constructors are likely operate upon function pointer or reference to callable object, std::function
is designed to act upon any compatible callable, that includes itself.
if you attempt to do that to do that to std::packaged_task
, the converting constructor may do something wrong or likely wouldn't compile because it wouldn't be able to work with instance of own class. A statement that looks like copy (but actually is .. a move? assignment?) would be possible.