Reputation: 145
Link https://cplusplus.github.io/LWG/issue2067 provides the following discussion:
Class template packaged_task is a move-only type with the following form of the deleted copy operations:
packaged_task(packaged_task&) = delete;
packaged_task& operator=(packaged_task&) = delete;
Note that the argument types are non-const. This does not look like a typo to me, this form seems to exist from the very first proposing paper on N2276. Using either of form of the copy-constructor did not make much difference before the introduction of defaulted special member functions, but it makes now an observable difference. This was brought to my attention by a question on a German C++ newsgroup where the question was raised why the following code does not compile on a recent gcc:#include <utility> #include <future> #include <iostream> #include <thread> int main() { std::packaged_task<void()> someTask([]{ std::cout << std::this_thread::get_id() << std::endl; }); std::thread someThread(std::move(someTask)); // Error here // Remainder omitted }
It turned out that the error was produced by the instantiation of some return type of std::bind which used a defaulted copy-constructor, which leads to a const declaration conflict with [class.copy] p8.
Some aspects of this problem are possibly core-language related, but I consider it more than a service to programmers, if the library would declare the usual form of the copy operations (i.e. those with const first parameter type) as deleted for packaged_task to prevent such problems.
Could anybody explain the meaning of the marked statement? I don't undestand how the missing const qualifer affects the compilation process, and how this behavior is explained in standard. What is the point of adding const to the parameter of the deleted copy constructor?
Upvotes: 3
Views: 236
Reputation: 275500
Here is a toy example:
struct problem {
problem()=default;
problem(problem&&)=default;
problem(problem&)=delete;
};
template<class T>
struct bob {
T t;
bob()=default;
bob(bob&&)=default;
bob(bob const&)=default;
};
int main() {
problem p;
problem p2 = std::move(p);
bob<problem> b;
bob<problem> b2 = std::move(b);
}
bob<problem>
fails to compile because the bob(bob const&)=default
errors out when it interacts with problem(problem&)=delete
.
Arguably the standard "should" error-out cleanly when it determines that it cannot implement bob(bob const&)
, and treat the =default
as =delete
(like it would if we had problem(problem const&)=delete
), but the standard wording isn't going to be flawless in this corner case of a corner case. And this corner of a corner case is going to be strange and quirky enough that I'm not certain a general rule that makes it translate =default
to =delete
would be right!
The fix if we problem(problem const&)=delete
(well, to packaged_task
) is going to be so much cleaner than anything we do to =default
ctor rules.
Now standard delving:
First, it is obvious that the implicitly declared copy constructor of bob<problem>
above is going to have signature bob(bob&)
in [class.ctor]. I won't even quote the standard for that, because lazy.
We go and explicitly default bob(bob const&)
copy ctor, which differs in signature from the one that would be implicitly declared.
There are rules about explicitly defaulting functions and their conflict with the signatures is in 11.4.2.
In Explicitly-defaulted functions[dcl.fct.def.default] 11.4.2/2
2 The type
T1
of an explicitly defaulted functionF
is allowed to differ from the typeT2
it would have had if it were implicitly declared, as follows:—(2.1)
T1
andT2
may have differing ref-qualifiers; and—(2.2) if
T2
has a parameter of typeconst C&
, the corresponding parameter ofT1
may be of typeC&
.If
T1
differs fromT2
in any other way, then:—(2.3) if
F
is an assignment operator, and the return type ofT1
differs from the return type ofT2
orT1
’s parameter type is not a reference, the program is ill-formed;—(2.4) otherwise, if
F
is explicitly defaulted on its first declaration, it is defined as deleted;—(2.5) otherwise, the program is ill-formed.
The defaulted one is T1
, which contains const&
not &
, so (2.2) doesn't apply.
My reading actually has it getting caught on (2.4); the type of bob(bob const&)
differs from the implicitly declared bob(bob&)
in an impermissible way; but first declaration is default
ed, so it should be delete
d.
I'm looking at the n4713 draft version; maybe an older version didn't have that clause.
Upvotes: 1