Why deleted copy constructor doesn't let to use other constructor with polymorphic type?
Asked Answered
G

1

4

I wonder why this program doesn't compile (the same behavior on msvc, gcc and clang):

#include <iostream>

using namespace std;

struct Action
{
    virtual void action()
    {
        cout << "Action::action()\n";
    }
};

struct ActionDecorator : Action
{
    ActionDecorator(const ActionDecorator&) = delete;
    ActionDecorator(Action & action) : origAction(action)
    {
    }

    void action() override
    {
        decoration();
        origAction.action();
    }

private:
    void decoration()
    {
        cout << "ActionDecorator::decoration()\n";
    }

    Action & origAction;
};

int main()
{
    Action action;
    ActionDecorator actionDecorator(action);
    ActionDecorator actionDecorator2(actionDecorator);
    actionDecorator2.action();
}

According to my expectation, deleted copy constructor should let construct ActionDecorator by other ActionDecorator instance, as it is polymorphic type of Action. Instead I have to explicit cast ActionDecorator instance to Action& as compiler complains about attempting to reference a deleted copy constructor. Is there some standard rule which explains such behavior?

Greasy answered 8/3, 2018 at 12:56 Comment(0)
D
6

Deleting a a function doesn't remove it from overload resolution. The function is merely defined as deleted. The purpose is to make the program ill-formed when overload resolution chooses that function.

Since the copy c'tor is a better match than the special base class c'tor you provided, overload resolution will always pick it if you don't cast.

How to best handle it is debatable. You could theoretically have the copy c'tor do a similar sort of wrapping. I'm torn about having a copy c'tor that doesn't copy, however. Your millage may very.

Another option, which I'm personally much more comfortable with, is to not provide public constructors as is. Instead, have clients create decorators via a regular named function. Something like this:

ActionDecorator decorate(Action& action) {
  return {action};
}

Now the class can truly remain non-copyable, and clients will never need to cast it themselves. If they pass an ActionDecorator to decorate, it will bind to an Action reference prior to constructing the instance. So it won't even consider the copy c'tor.

The class will have to be movable, for this to work prior to C++17, however.

Domela answered 8/3, 2018 at 12:59 Comment(5)
A defaulted move constructor or assignment operator (15.8) that is defined as deleted is excluded from the set of candidate functions in all contexts.Iqbal
@Iqbal - This one is not default, it's deleted. It's an important distinction.Domela
@Iqbal - And also, this isn't a move operation, it's a copy operation. You can't exclude them from the overload set in any way.Domela
Thanks, it sems you are right. Do you know why deleted functions aren't removed from overload resolution? I wonder what issues it would implied (I assume there are some as it isn't implemented this way).Greasy
@Greasy - Your own use case is one example. It'd be pretty bad to have something look like a copy, but not actually be a copy (the thing I felt uncomfortable with). The purpose of deleting functions is to ultimately signal at compile time that the client is attempting an invalid operation, instead of doing something else silently. The only exemption is implicitly deleted move operations (so as to not break legacy code). That's the toys we have to play with, I'm afraid.Domela

© 2022 - 2024 — McMap. All rights reserved.