This is an unofficial snapshot of the ISO/IEC JTC1 SC22 WG21 Core Issues List revision 115e. See http://www.open-std.org/jtc1/sc22/wg21/ for the official list.
2024-11-11
By the letter of the standard, the conversions required to make auto_ptr work should be accepted.
However, there's good reason to wonder if there isn't a bug in the standard here. Here's the issue: line 16 in the example below comes down to
copy-initialize an auto_ptr<Base> from an auto_ptr<Derived> rvalueTo do that, we first look to see whether we can convert an auto_ptr<Derived> to an auto_ptr<Base>, by enumerating the constructors of auto_ptr<Base> and the conversion functions of auto_ptr<Derived>. There's a single possible way to do the conversion, namely the conversion function
auto_ptr<Derived>::operator auto_ptr<Base>()(generated from the template). (The constructor auto_ptr<Base>(auto_ptr_ref<Base>) doesn't work because it requires a user-defined conversion on the argument.)
So far, so good. Now, we do the copy step:
direct-initialize an auto_ptr<Base> from an auto_ptr<Base> rvalueThis, as we've gone to great lengths to set up, is done by calling the conversion function
auto_ptr<Base>::operator auto_ptr_ref<Base>()(generated from the template), and then the constructor
auto_ptr<Base>(auto_ptr_ref<Base>)(generated from the template).
The problem with this interpretation is that it violates the long-standing common-law rule that only a single user-defined conversion will be called to do an implicit conversion. I find that pretty disturbing. (In fact, the full operation involves two conversion functions and two constructors, but "copy" constructors are generally considered not to be conversions.)
The direct-initialization second step of a copy-initialization was intended to be a simple copy — you've made a temporary, and now you use a copy constructor to copy it. Because it is defined in terms of direct initialization, however, it can exploit the loophole that auto_ptr is based on.
To switch to personal opinion for a second, I think it's bad enough that auto_ptr has to exploit a really arcane loophole of overload resolution, but in this case it seems like it's exploiting a loophole on a loophole.
struct Base { // 2 static void sink(auto_ptr<Base>); // 3 }; // 4 struct Derived : Base { // 5 static void sink(auto_ptr<Derived>); // 6 }; // 7 auto_ptr<Derived> source() { // 8 auto_ptr<Derived> p(source()); // 9 auto_ptr<Derived> pp(p); // 10 Derived::sink(source()); // 11 p = pp; // 12 p = source(); // 13 auto_ptr<Base> q(source()); // 14 auto_ptr<Base> qp(p); // 15 Base::sink(source()); // 16 q = pp; // 17 q = source(); // 18 return p; // 19 return source(); }Derek Inglis:
It seems clear to me that the result of this direct initilization must be the second standard conversion sequence in a user defined conversion sequence. Otherwise the resulting conversion sequence is not an implicit conversion sequence. By the letter of the standard, the sequence of conversions making up a copy-initialization must be an implicit conversion sequence.
Paragraph 3 of 7.3 [conv]:
An expression e can be implicitly converted to a type T if and only if the declaration "T t=e;" is well-formed, for some invented temporary variable t (9.4 [dcl.init]).
Paragraph 1 of 12.2.4.2 [over.best.ics]:
An implicit conversion sequence is a sequence of conversions used to convert an argument in a function call to the type of the corresponding parameter of the function being called. The sequence of conversions is an implicit conversion as defined in 7.3 [conv], which means it is governed by the rules for initialization of an object or reference by a single expression (9.4 [dcl.init], 9.4.4 [dcl.init.ref]).Sentence 1 of paragraph 12 of 9.4 [dcl.init]:
The initialization that occurs in argument passing ... is called copy-initialization and is equivalent to the formT x = a;
For me, these sentences imply that all sequences of conversions permitted on a function argument must be valid implicit conversion sequences.
The 'loophole' can be closed by adding a sentence (or note) to the section describing the 'direct initialization second step of a copy initialization' stating that the copy initialization is ill-formed if the conversion sequence resulting from the direct initialization is not a standard conversion sequence.
(See also issue 177 and paper J16/00-0009 = WG21 N1232.)
Proposed resolution (10/00):
Change 12.2.4.2 [over.best.ics] paragraphs 3 and 4 from
Except in the context of an initialization by user-defined conversion (12.2.2.5 [over.match.copy], 12.2.2.6 [over.match.conv]), a well-formed implicit conversion sequence is one of the following forms:
- a standard conversion sequence (12.2.4.2.2 [over.ics.scs]),
- a user-defined conversion sequence (12.2.4.2.3 [over.ics.user]), or
- an ellipsis conversion sequence (12.2.4.2.4 [over.ics.ellipsis])
In the context of an initialization by user-defined conversion (i.e., when considering the argument of a user-defined conversion function; see 12.2.2.5 [over.match.copy], 12.2.2.6 [over.match.conv]), only standard conversion sequences and ellipsis conversion sequences are allowed.
to
A well-formed implicit conversion sequence is one of the following forms:
- a standard conversion sequence (12.2.4.2.2 [over.ics.scs]),
- a user-defined conversion sequence (12.2.4.2.3 [over.ics.user]), or
- an ellipsis conversion sequence (12.2.4.2.4 [over.ics.ellipsis])
However, when considering the argument of a user-defined conversion function that is a candidate by 12.2.2.4 [over.match.ctor] when invoked for the copying of the temporary in the second step of a class copy-initialization, or by 12.2.2.5 [over.match.copy], 12.2.2.6 [over.match.conv], or 12.2.2.7 [over.match.ref] in all cases, only standard conversion sequences and ellipsis conversion sequences are allowed.