On Fri, 11 Oct 2024, 19:21 Jonathan M Davis via Digitalmars-d, <digitalmars-d@puremagic.com> wrote:
On Friday, October 11, 2024 12:48:45 AM MDT Manu via Digitalmars-d wrote:
> They initialise an S from another S... I'm not sure what you mean by "they
> do different things"?
>
> I've been wondering, why do you need to detect that a constructor is a copy
> or move constructor? That concept seems like a big internal hack. Why do
> normal overload selection semantics fail to determine the proper selection?
> It's not clear why it's useful or important that copy and move constructors
> are separate from all other constructors? They're not special; they just
> happen to accept a self-type as an init argument... the only reason I can
> think, is to identify the case where they're not present, and some
> copy/move code must by synthesised in lieu... what else hangs off that
> knowledge?

Another example of how they're treated as special is that if you declare no
constructors for a struct, you get implicit construction syntax for that
type. e.g.

You literally gave the example that I gave just one sentence prior. I'm very aware of this.
I asked for any additional cases... can you think of any?


    struct S
    {
        int x;
        int y;
    }

    void main()
    {
        auto s1 = S(42);
        auto s2 = S(42, 27);
    }

As soon as you declare any constructors, you no longer get that implicit
construction syntax, and you can only construct the type with the
constructors that you provided - except that copy constructors (and
eventually move constructors) are not treated as constructors in this
context. Declaring a copy constructor does not get rid of the implicit
construction syntax. I've specifically had to deal with this situation when
wrapping D code in order to create the same constructors that the D type has
but in another language, and the fact that a copy constructor is __ctor just
like all of the other constructors except for postblit constructors makes
this more of a pain for no good reason.

It's also the case that some metaprogramming needs to know whether a type
has a copy constructor or move constructor in order to do the right thing
(e.g. the lowerings for assigning slices of arrays to each other have to
take that into account as do types like std.typecons.Nullable). Hiding them
as normal constructors just makes that harder.

All in all, I don't understand why you want to treat copy constructors or
move constructors as if they were normal constructors. Unlike normal
constructors, they get used when you do not call them, and you usually don't
call them at all. They also have a huge impact on the semantics of how a
type is actually used in many situations in a way that normal constructors
do not. Treating them as normal constructors makes it harder for the user to
see what's going on, and anyone generating code via metaprogramming then has
a harder time doing that, because detecting them is more complicated.

What benefit do you see in treating copy constructors or move constructors
like they're normal constructors instead of explicitly treating them as
special - especially given that the compiler already has to treat them as
special in order to generate the correct code in many cases?

Semantic uniformity. Special casing random things is D's main claim to fame and the source of almost everything wrong with the language. 
The overload selection rules should make the proper choice.

For what it's worth, I sympathize with your concern about template copy constructors or such; what I do, is declare `this(ref typeof(this))`, and then beside that declare my other copy-like-constructors with an if(!is(T == typeof(this)) constraint, or some similar constraint that effectively excludes the one case represented by the copy constructors.