Jason McKesson Mon, 13 May 2024 11:33:39 -0400 via Std-Proposals
<std-proposals@lists.isocpp.org> wrote:

But that isn't even a delegating constructor. At least, not in terms
of the C++ meaning of that term, as defined in [class.base.init]/6.

If you mean it in the vernacular sense of "it calls some other
constructor", OK, but your initial post seems invalid. This isn't a
performance optimization; you're just asking for the thing you asked
for earlier in a different thread. So I don't know why you tried to
pass off the same request as a performance optimization.

In any case, you need to address a very important point.

What you want introduces an irregularity in the language. At the
present time, what this code means and does is clear and unambiguous.
Furthermore, it does the same thing it would do in any other case. If
you do this:

```
some_type t;
base u(t);
```

This will do the same thing, regardless of where it exists.

What you want to do is change the language to make it irregular. Which
constructor will be called now will depend on the context of that
constructor call's location. In some places, it will call the template
constructor. In other places it won't.

Irregularities are... bad, actually. Making code that looks the same
but behaves differently based on where that code is located is not a
good thing. It may sometimes be necessary or extremely useful. But you
need to actually make that argument. You need to explain why the cost
of irregularity is worth it.

In particular, is this an idiom that people actually use? Is this a
common confluence of code that people see a lot? Because on the face
of it, the special case seems so rare that I don't buy that it needs
to be addressed. In order to encounter this problem, the following set
of things needs to happen:

1. A user writes a class intended to be used as a base class (note: it
is a code smell to use a class as a base class if that class isn't
intended for that purpose. It's not always wrong, but it should be
looked at as dubious).

2. The base class has a template constructor that takes exactly one
parameter by value (which suggests that this constructor has a meaning
that is very distinct from copying it. That is, it is not intended to
take itself as a parameter).

3. A user writes a class derived from this class. Seemingly unaware of
the base class's interface, the user attempts to invoke the base
class's copy constructor by passing itself as a parameter to the base
class constructor.

Where exactly things went wrong here is debatable. I would argue that
#1 and #2 are inconsistent with each other. If you're making a class
that is intended to be a base class, you probably shouldn't create a
template constructor that would interfere with initializing that base
class. Note that types like `std::any`, despite not being intended to
be a base class, don't take their template parameter by value. But an
argument could be made that step 3 also is a bug, as the user should
have looked at the class's constructors before trying to use them.

Regardless, the key question to me is this: how often do these
circumstances *actually* happen in the real world? Do they happen
often enough to warrant creating inconsistency in the behavior of the
language? Is there some important C++ idiom that's being blocked by
this?

This has to happen often enough to pay the costs of making the
language inconsistent.

I apologize for any confusion caused by my example. 

    Calling a constructor for u(t) directly or indirectly, wherever it is, the argument is passed in a uniquely deterministic way which is not limited to calls to the delegated constructors. It applies to all constructor calls, everywhere. Just forget about template constructors and initial lists and focus on constructor calls and how to pass an argument.

    But why would I recommend that copying of the same type or its subtypes should be prohibited before constructing an object? (If copying of the same type or its subtypes is prohibited before constructing an object, the argument will always be passed by reference if it is an object of the current class or its subclasses, and infinite loops will not occur in constructor calls.)

    Currently in C++, the behavior of function arguments is in most cases determined by the signature of the function. Function signatures bear too many responsibilities. While this relieves lots of the burdens on the caller, it also prevents the right (decide the behavior of function arguments) from the user and makes it obscure and difficult to understand. The behavior of argument is completely solidified in the function signature in most cases in C++!  

    The function signature only needs to convey function's intents to the caller. It should be the user who has the right to decide how to pass arguments to the function, otherwise overloading will be very restrictive. For example, the move constructor in C++ communicates the intention of the move construction to the caller through the && symbol.  And when constructing a new object, the caller must use std::move to call the corresponding constructor. 
    Similarly, We can define an intent communicator for the copy constructor, i.e. std::ref, to bear the responsibility of obtaining the object reference for the class name and & symbol in the function signature. We can define std::copy for the constructor that passes arguments by value, taking the responsibility of copying the object for the class name in the function signature. 

    The function's writer only needs to decide what will happen to the argument which is told to the caller via function declaration. It is the caller who decide the behavior of function arguments according to the function declaration and the argument's usage environment. In fact, what will happen are determined by the compiler, which determines the behavior of the argument based on the syntax which is provided by the caller. 

    Currently in C++, the syntax is provided by function declarations, but this approach does not provide a unified model of how the arguments are passed. The argument passed to function by value is not the real argument passed to function, it is the copy of the so-called argument. We are deceived and fooled by the model which masks the essence of things with convenient candy. The model is conceptually incomplete. 

     The caller determines the diversity of overloading by providing various types of parameters. And the caller determines the powerful expressiveness of the assignment expression, not the producer of the rvalue. This caller's decision rights provides a unified model for all circumstance. This may seem extreme but handles various situations very uniformly. It is a huge subject and how to call the constructor is a good starting point. 

    In the subject of constructor calls, the behavior of delegation constructor's argument can be determined by the writer at the time the constructor is formed, and the desired behavior is delegated to the compiler according to the corresponding syntax. On the contrary, users who call a constructor can also freely decide how to pass arguments to the constructor based on the declaration of the constructor and the calling environment. It is reasonable and there is no irregularity here. 

    The syntax of C++ takes some simple ways to free users from boring work. But as new features (for example, rvalue references introduced in C++11) were introduced into C++, chaos emerged, such as inconsistent syntax for calling constructors and carelessness leading to infinite loops in calling constructors.

    We can say that this is the user's responsibility and that's the user's fault. We take this for granted simply because we are already familiar with how C++ works. From a newcomer’s perspective, isn’t this strange, difficult to understand, and irrational?

    Those simple coding ways that C++ syntax provides us is indeed very beneficial, in a practical, historically respectful way. But chaos is ultimately bad and unreasonable, making it more difficult to learn and understand. 

    This irregularity or inconsistency in C++ syntax brings us great convenience, but the above-mentioned infinite loop, which can be resolved within the rule, does not bring us any benefits. The convenience provided by C++ syntax relieves the real responsibility of users,  while the confusion caused by the syntax convenience is blamed on users.

    Due to reasons such as efficiency, habits, history, etc., there are always special situations in reality. The desire of how to pass arguments to function is indicated by the function declaration, but the behavior of the function argument must be the caller's responsibility (In actual language design and implementation, we often delegate it to the compiler).

I went too far, and the title was too small to cover the topic at hand, but it was the best entry point to introduce it. The above statement seems to be extreme and wrong, but it has stronger descriptive power and a more convincing form of explanation, and may be able to handle various situations very uniformly. I hope the above statement can express my thoughts well, and I apologize for the obscurity and repetitiveness of the above narratives.