Why the initializer of std::function has to be CopyConstructible?
std::function uses type erasure internally, so F has to be CopyConstructible even if the particular std::function object you are using is never copied.
A simplification on how type erasure works:
class Function
{
struct Concept {
virtual ~Concept() = default;
virtual Concept* clone() const = 0;
//...
}
template<typename F>
struct Model final : Concept {
explicit Model(F f) : data(std::move(f)) {}
Model* clone() const override { return new Model(*this); }
//...
F data;
};
std::unique_ptr<Concept> object;
public:
template<typename F>
explicit Function(F f) : object(new Model<F>(std::move(f))) {}
Function(Function const& that) : object(that.object->clone()) {}
//...
};
You have to be able to generate Model<F>::clone()
, which forces F to be CopyConstructible.
Rationale behind making std::function require copy constructor
std::function
could take a move-only callable, but only by restricting itself to being move-only always. It uses type-erasure to store the object, so its static behavior must represent the lowest-common-denominator functionality that it requires of the objects it stores.
Here's the thing though: there are a lot of places in C++ and its standard library that expect that a callable shall be copyable. And by "lot", I mean the entirety of the standard algorithms library. Even the C++20 concept indirect_unary_predicate
requires copy_constructible
. Essentially, all algorithms are given the freedom to copy the given function at will; they even take those functions by value.
A move-only std::function
type could never be used with any such algorithm. And being move-only is the only way for std::function
to take move-only types.
Why is std::is_copy_constructible_vstd::vectorMoveOnlyType true?
std::vector
and other containers (except std::array
) are specified to have a copy constructor. This is not specified to be conditional on whether or not the element type is copyable. Only instantiation of the copy constructor's definition is forbidden if the element type is not copyable.
As a result std::is_copy_constructible_v
on the container will always be true
. There is no way to test whether an instantiation of a definition would be well-formed with a type trait.
It would be possible to specify that the copy constructor is not declared or excluded from overload resolution if the element type is not copyable. However, that would come with a trade-off which is explained in detail in this blog post: https://quuxplusone.github.io/blog/2020/02/05/vector-is-copyable-except-when-its-not/.
In short, if we want to be able to use the container with an incomplete type, e.g. recursively like
struct X {
std::vector<X> x;
};
then we cannot determine whether X
is copyable when the container class is instantiated. Therefore the declaration of the copy constructor cannot be made dependent on this property.
Since C++17 the standard requires std::vector
, std::list
and std::forward_list
, but not the other containers, to work like this with incomplete types.
Why can't I move a mutable function that contains a moved future?
function_which_should_be_movable
is of type std::function
. According to cppreference:
template< class F > function( F f );
F
must meet the requirements
ofCallable
andCopyConstructible
.
The lambda expression you try to use to construct an std::function
object is not copyable, and hence the problem.
As to why std::function
has this requirement, please see this question: Why the initializer of std::function has to be CopyConstructible? (which is asked exactly by myself). Simply put, the type erasure technique used by std::function
will instantiate the copy-constructor of F
. This happens regardless of whether you, the user of the std::function
object, actually used this copy-constructor or not.
Copy Construction in Initializer Lists
The issue is that this type:
struct NonCopyable {
NonCopyable() = default;
NonCopyable(const NonCopyable&) = delete;
};
is trivially copyable. So as an optimization, since std::initializer_list
is just backed by an array, what libstdc++ is doing is simply memcpying the the whole contents into the vector
as an optimization. Note that this type is trivially copyable even though it has a deleted copy constructor!
This is why when you make the default constructor user-provided (by just writing ;
instead of = default;
), is suddenly doesn't compile anymore. That makes the type no longer trivially copyable, and hence the memcpy path goes away.
As to whether or not this behavior is correct, I am not sure (I doubt there's a requirement that this code must not compile? I submitted 89164 just in case). You certainly want libstdc++ to take that path in the case of trivially copyable - but maybe it needs to exclude this case? In any case, you can accomplish the same by additionally deleting the copy assignment operator (which you probably want to do anyway) - that would also end up with the type not being trivially copyable.
This didn't compile in C++14 because you could not construct the std::initializer_list
- copy-initialization there required the copy constructor. But in C++17 with guaranteed copy elision, the construction of std::initializer_list
is fine. But the problem of actually constructing the vector
is totally separate from std::initializer_list
(indeed, this is a total red herring). Consider:
void foo(NonCopyable const* f, NonCopyable const* l) {
std::vector<NonCopyable>(f, l);
}
That compiles in C++11 just fine... at least since gcc 4.9.
Why require copy constructor for packaged_task in VS
This is a known bug in MSVC's packaged_task
implementation. They're storing the callable within std::function
, which requires that the argument be copy-constructible.
Related Topics
What Are the Operations Supported by Raw Pointer and Function Pointer in C/C++
Copy Constructor VS. Return Value Optimization
C++ Calculating More Precise Than Double or Long Double
Is Maximum_Wait_Objects Really 64
Opencv on MAC Is Not Opening Usb Web Camera
Using Bind1St for a Method That Takes Argument by Reference
Conversion from Derived** to Base**
Strange "Unsigned Long Long Int" Behaviour
How to Read Frames from Videocapture from Secondary Webcam with Opencv
Initializing a Ublas Vector from a C Array
Parse Int or Double Using Boost Spirit (Longest_D)
How to Speed Up This Histogram of Lut Lookups
What Is Aggregate Initialization
How to Compile C++ with C++11 Support in MAC Terminal
Invalid Initialization of Non-Const Reference with C++11 Thread