Skip to content

[dcl.init] Guaranteed copy elision for direct-initialization with a constructor #704

Open
@YurkoFlisk

Description

@YurkoFlisk

Full name of submitter: Yuriy Prokopets

Reference (section label): dcl.init

Issue description: There is CWG 2327 "Copy elision for direct-initialization with a conversion function" and P2828R2 addressing it, which intends to allow copy elision for direct-initialization when copy/move constructor's parameter binds to the result of a conversion function, reflecting what major implementations already do.

In the main provided example:

struct Cat {};
struct Dog { operator Cat(); };

Dog d;
Cat c(d);

it's clear that, if we had a Cat(Dog) constructor instead of a conversion operator, the overload resolution would chose it and copy elision wouldn't be necessary. Lack of need to address cases like this is probably why CWG 2327 and P2828R2 deal just with conversion operators.

However, consider this example:

struct Dog { int t; };
struct Cat {
    Cat(Cat&&) = delete; 
    Cat(Dog) {}
};

Dog d;
Cat c({{d}});

(godbolt)

Only Cat(Cat&&) and (implicitly deleted) Cat(const Cat&) are viable and, thus, Cat(Cat&&) is selected by overload resolution1. GCC and EDG accept the code in C++17-23 modes, but reject in C++14 mode, suggesting that, according to them, this is a case of guaranteed copy elision. Clang and MSVC reject because the constructor is deleted. Per the current draft, Clang and MSVC are correct, and P2828R2 wouldn't change that.

Suggested resolution: following GCC and EDG, implement guaranteed copy elision for this case. For example, modify P2828R2 additions like this (modifications in bold italics):

Additions to [dcl.init.general]/16.6.2:

... and the implicit conversion sequence ([over.best.ics.general]) for the constructor's first parameter would bind that parameter to the object constructed by a constructor of the class of destination or the result object of a conversion function whose return type, ignoring cv-qualification, is the class of the destination ...

Additions to [dcl.init.list]/3.7:

... and the implicit conversion sequence ([over.best.ics.general]) for the constructor's first parameter would bind that parameter to the object constructed by a constructor of T or the result object of a conversion function whose return type is T (ignoring cv-qualification) ...

Also add the example above. Maybe also change the term conversion-initialization to something that would reflect these modifications.

Another alternative is to somehow make overload resolution fail in the example making such initializations ill-formed, if they are considered undesirable, but that could break code.

Footnotes

  1. though due to CWG 1536, as one of byproducts, there's an underspecification regarding what exactly reference bindings in the second standard conversion sequence of ISC "bind" to when considered in [over.ics.rank]/3.2.3 for comparing ICS' for initializer list arguments, any sane resolution to that would likely result in "bound to rvalue" for this case (or equivalent behaviour), which is what users would expect and what compilers implement.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions