C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Should copying of the same type or its subtype be prohibited before constructing an object?

From: David wang <wangjufan_at_[hidden]>
Date: Sun, 19 May 2024 22:33:46 +0800
Jason McKesson Mon, 13 May 2024 11:33:39 -0400 via Std-Proposals
<std-proposals_at_[hidden]> 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.

Received on 2024-05-19 14:34:03