Jump to page: 1 2
Thread overview
copy must be const?!?
Jul 24
Dom DiSc
Jul 24
Dennis
Jul 25
Dom DiSc
Jul 25
Dom DiSc
Jul 26
Dom DiSc
Jul 26
Dom DiSc
Jul 26
Dom DiSc
Jul 29
Dom DiSc
July 24

In following code:

int test(int x) { return --x; }
T test2(T)(T x) { return --x; }

void main()
{
   const int v = 3;
   writeln(test(v));
   writeln(test2(v)); // doesn't compile
}

test2 (just like test) works on a copy of x. Why is this copy const?!? If I copy a const or immutable object, most of the time I do so because I want to work with this value. Getting a const copy is never useful. If it were, why is it not const in test?

This also annoys me if I copy write protected files on windows - why on earth should the copy be write protected?

Is there a way to tell the compiler that it should discard "const" and "immutable" if it needs to create a copy?
Unqual!T doesn't work :-(

July 24

On Wednesday, 24 July 2024 at 15:08:28 UTC, Dom DiSc wrote:

>

Why is this copy const?!? If I copy a const or immutable object, most of the time I do so because I want to work with this value.

Because the compiler instantiates test2!T deriving T from the type of the argument you pass to x, which is const(int). This is indeed an annoyance with const, and there's even some special treatment for arrays such that passing a const char[] will instantiate with const(char)[], which is necessary to make ranges work. There's a DIP trying to generalize this behavior to user-defined types, but it's WIP: https://forum.dlang.org/thread/ut4f3m$2e4a$1@digitalmars.com

>

Is there a way to tell the compiler that it should discard "const" and "immutable" if it needs to create a copy?
Unqual!T doesn't work :-(

When you add const or immutable before the template type parameter, it will infer T as simply int:

T test2(T)(immutable T x) { return --T(x); }
July 25

On Wednesday, 24 July 2024 at 15:40:28 UTC, Dennis wrote:

> >

Is there a way to tell the compiler that it should discard "const" and "immutable" if it needs to create a copy?
Unqual!T doesn't work :-(

When you add const or immutable before the template type parameter, it will infer T as simply int:

T test2(T)(immutable T x) { return --T(x); }

Woah. Is this @safe?
Ok, I know this is a fresh copy, that hopefully doesn't go to non-volatile memory, but I'm always sceptical casting away const.

July 25
On Thursday, July 25, 2024 12:50:04 AM MDT Dom DiSc via Digitalmars-d-learn wrote:
> On Wednesday, 24 July 2024 at 15:40:28 UTC, Dennis wrote:
> >> Is there a way to tell the compiler that it should discard "const" and "immutable" if it needs to create a copy? Unqual!T doesn't work :-(
> >
> > When you add `const` or `immutable` before the template type parameter, it will infer T as simply `int`:
> >
> > ```D
> > T test2(T)(immutable T x) { return --T(x); }
> > ```
>
> Woah. Is this @safe?
> Ok, I know this is a fresh copy, that hopefully doesn't go to
> non-volatile memory, but I'm always sceptical casting away const.

It's not a cast. Casts in D use the keyword, cast - e.g.

    return --(cast(T)x);

Rather, Dennis' solution is constructing a value of the given type. For it to work, T must be constructible from an immutable T - which works with integer types but won't actually work with most types. It also won't work with pointer types or reference types, since those would need new when constructing them.

And no, in general, you don't want to be casting away const or immutable. There are cases where it can work (e.g. if the cast does a copy, which it would with an integer type), but if you ever cast away const or immutable and mutate the result when the result isn't a fully independent copy, you're violating the type system.

Usually, in cases like this one, you'd just require that the caller pass a type that's mutable, forcing the caller to do whatever is appropriate to get a mutable copy rather than trying to do such a copy internally, but Dennis' solution will work with some types. That could be done by just letting the caller get an error (like you were), or the more user-friendly solution is to use a template constraint that checks that the operations that you need to use actually work with the given type, e.g.

T test2(T)(T x)
    if(is(typeof(--x)))
{
    return --x;
}

So, then you get an error about the template constraint failing rather than the template simply not being able to be instantiated. And aside from the issue of const, that's arguably a better solution anyway, since then it will catch any type which doesn't work with the decrement operator.

Then if you wanted to pass a const int, some of the options would be

test2(int(i));

test2(cast(int)(i));

test2!int(i); // works, because the compiler will copy i

In general though, you can't currently pass a const type to a templated function and then have it instantiated without const. The only types that that currently works with are dynamic arrays. Templated functions which take dynamic arrays are instantiated with the same type you get when slicing them, which means that const gets removed from the array itself but not from the element type, e.g.

const int[] arr;
static assert(is(typeof(arr) == const(int[])));
static assert(is(typeof(arr[]) == const(int)[]));

So, if you have something like

immutable string foo = "hello";

and you pass it to a templated function, the type will be treated as string, whereas with a user-defined type - or with any built-in types which aren't dynamic arrays - the templated function is instantiated with exactly the type that you pass it.

- Jonathan M Davis



July 25

On Thursday, 25 July 2024 at 08:42:29 UTC, Jonathan M Davis wrote:

>

It's not a cast. Casts in D use the keyword, cast - e.g.

return --(cast(T)x);

Rather, Dennis' solution is constructing a value of the given type. For it to work, T must be constructible from an immutable T - which works with integer types but won't actually work with most types.

Ah! Good, so at least here I can use it. Fine.
Should also wor if the type has an appropriate constructor, e.g. a copy constructor that takes a const parameter and returns a mutable copy (as I would expect every copy constructor to do), so at least in code I write, I will never have a problem.

>

It also won't work with pointer types or reference types, since those would need new when constructing them.

Of course. For those that also wouldn't work for functions instead of templates.

>

And no, in general, you don't want to be casting away const or immutable. There are cases where it can work (e.g. if the cast does a copy, which it would with an integer type)

But a parameter given by value is ALWAYS a copy.
So if a copy is done, the copy should be mutable. I can't see why that should ever be a problem. The compiler should never create new values const or immutable unless explicitly advised to do so.
E.g.

void fun(myType x) { }

called with immutable object should create a mutable x, because

  myType x = immutableObject;

will also create a mutable x. If I want an immutable x, i can do

  immutable myType = immutableObject;
void fun(T)(T x) if (is(Unqual!T==myType)) { }

should also create a mutable x, for the same reason, because if I want an immutable x, I can do

void fun(T)(immutable(T) x) if (is(Unqual!T==myType)) { }

Therefore I consider the current behaviour a bug.

July 25
On Thursday, July 25, 2024 6:00:58 AM MDT Dom DiSc via Digitalmars-d-learn wrote:
> > And no, in general, you don't want to be casting away const or immutable. There are cases where it can work (e.g. if the cast does a copy, which it would with an integer type)
>
> But a parameter given by value is ALWAYS a copy.
> So if a copy is done, the copy should be mutable. I can't see why
> that should ever be a problem. The compiler should never create
> new values const or immutable unless explicitly advised to do so.

It has to be a _full_, independent copy. If you're talking about integer types, that's a non-issue, but if you're talking about types with any indirections, it becomes a big issue. If the type contains any pointers, dynamic arrays, class references, etc. then the copy can't be mutable unless you're dealing with a struct with a copy constructor that does a deep copy of all of the member variables. So, in the general case, you cannot copy a value and expect to get a mutable result.

> E.g.
> ```d
> void fun(myType x) { }
> ```
>
> called with immutable object should create a mutable x, because
>
> ```d
>    myType x = immutableObject;
> ```
>
> will also create a mutable x. If I want an immutable x, i can do
>
> ```d
>    immutable myType = immutableObject;
> ```
>
> ```d
> void fun(T)(T x) if (is(Unqual!T==myType)) { }
> ```
>
> should also create a mutable x, for the same reason, because if I want an immutable x, I can do
>
>
> ```d
> void fun(T)(immutable(T) x) if (is(Unqual!T==myType)) { }
> ```
>
> Therefore I consider the current behaviour a bug.

It's most definitely not a bug that IFTI (Implicit Function Template Instantiation) instantiates the template with the exact type that it's given. In the general case, that's what you want - particularly since many, many types (probably the vast majority of types) cannot be const or immutable and then result in a mutable copy when they're copied. That really only works with very simple types with no indirections. In the general case, there is no expectation whatsoever that it's going to be possible to get a mutable copy from a const or immutable variable, and generic code that expects that to work is very likely to run into problems very quickly.

Now, the fact that code such as

    void fun(T : const U, U)(U x)
        if(is(immutable T == immutable U))
    {
        ...
    }

or code like

    void fun(T)(Unqual!T x)
    {
    }

doesn't work with IFTI and requires explicit instantation is definitely a deficiency in IFTI's current capabilities, but in the general case, we very much want

    void fun(T)(T x) {}

to be instantiated as fun!(const Foo) if it's passed a const Foo.

For some types, we _would_ like the ability to tell the compiler to implicitly instantiate function templates with a tail-const version similar to what we get with dynamic arrays, but that really only makes sense for certain types such as ranges.

But either way, it would cause quite a few problems if IFTI started instantiating templates in general with mutable instead of the actual type that it's given, because it's extremely common that const and immutable types cannot be converted to mutable.

- Jonathan M Davis



July 25

On Thursday, 25 July 2024 at 13:07:03 UTC, Jonathan M Davis wrote:

>

On Thursday, July 25, 2024 6:00:58 AM MDT Dom DiSc via Digitalmars-d-learn wrote:

> >

And no, in general, you don't want to be casting away const or immutable. There are cases where it can work (e.g. if the cast does a copy, which it would with an integer type)

But a parameter given by value is ALWAYS a copy.
So if a copy is done, the copy should be mutable. I can't see why
that should ever be a problem. The compiler should never create
new values const or immutable unless explicitly advised to do so.

It has to be a full, independent copy. If you're talking about integer types, that's a non-issue, but if you're talking about types with any indirections, it becomes a big issue. If the type contains any pointers, dynamic arrays, class references, etc. then the copy can't be mutable unless you're dealing with a struct with a copy constructor that does a deep copy of all of the member variables. So, in the general case, you cannot copy a value and expect to get a mutable result.

>

E.g.

void fun(myType x) { }

called with immutable object should create a mutable x, because

   myType x = immutableObject;

will also create a mutable x. If I want an immutable x, i can do

   immutable myType = immutableObject;
void fun(T)(T x) if (is(Unqual!T==myType)) { }

should also create a mutable x, for the same reason, because if I want an immutable x, I can do

void fun(T)(immutable(T) x) if (is(Unqual!T==myType)) { }

Therefore I consider the current behaviour a bug.

It's most definitely not a bug that IFTI (Implicit Function Template Instantiation) instantiates the template with the exact type that it's given. In the general case, that's what you want - particularly since many, many types (probably the vast majority of types) cannot be const or immutable and then result in a mutable copy when they're copied. That really only works with very simple types with no indirections. In the general case, there is no expectation whatsoever that it's going to be possible to get a mutable copy from a const or immutable variable, and generic code that expects that to work is very likely to run into problems very quickly.

Now, the fact that code such as

void fun(T : const U, U)(U x)
    if(is(immutable T == immutable U))
{
    ...
}

or code like

void fun(T)(Unqual!T x)
{
}

doesn't work with IFTI and requires explicit instantation is definitely a deficiency in IFTI's current capabilities, but in the general case, we very much want

void fun(T)(T x) {}

to be instantiated as fun!(const Foo) if it's passed a const Foo.

For some types, we would like the ability to tell the compiler to implicitly instantiate function templates with a tail-const version similar to what we get with dynamic arrays, but that really only makes sense for certain types such as ranges.

But either way, it would cause quite a few problems if IFTI started instantiating templates in general with mutable instead of the actual type that it's given, because it's extremely common that const and immutable types cannot be converted to mutable.

It’s wild how C++ and D disagree on this one. In C++, a copy constructor must produce a mutable copy. Now, C++ and D disagree on what const means and therefore what counts as a mutable copy. But, and this is the key takeaway, C++ never infers const on a copy. Never. It does infer const on a reference, of course.

Nothing of this is out of reach for D. A type for which const objects can’t be copied to initialize a mutable one simply isn’t copyable and therefore can’t be passed by value. A type for which const rvalues can’t be used to initialize a mutable variable aren’t movable.

C++ solves this by binding values by universal reference:

template<typename T>
void f(T&&);

This f eats anything. For rvalues of type X, T is inferred X, the rvalue is materialized in the caller and passed by (rvalue) reference to f. For lvalues, T is inferred X& so that the parameter becomes of type (T&)&& which is the same as T&, thus f ends up binding the value by (lvalue) reference.

D doesn’t have that. In that regard, D is approximately where C++ was before C++11. D’s ref can’t bind lvalues, which is probably a good thing. In one of my recent DIP Ideas posts, I outlined @universal ref and @rvalue ref.

The best D can do today is this:

void f(T)(auto ref T);

For lvalues, that’s great. Nothing to complain. Rvalues should be moved into the parameter. For that, the type must support moves, which most types do, but the compiler doesn’t check if the move could give you a mutable object. Even if it can, the compiler will give you a qualified parameter.

Because of D’s transitive qualifiers, a mutable copy may not be possible. In that case, we’d have two options: Say the type isn’t copyable, or say “here’s your ‘copy,’ but it’s const.” However, copying a type for which there’s a copy constructor that can give you a mutable object is a no-no.

July 26
On Thursday, 25 July 2024 at 13:07:03 UTC, Jonathan M Davis wrote:

> It's most definitely not a bug that IFTI (Implicit Function Template Instantiation) instantiates the template with the exact type that it's given.

The "principle of least astonishment" certainly supports this behavior.  To check my understanding, I forced the type of instantiation:

  writeln(test2!int(v));

and that worked as I expected.

Andy

July 26
On Friday, 26 July 2024 at 02:34:12 UTC, Andy Valencia wrote:
> On Thursday, 25 July 2024 at 13:07:03 UTC, Jonathan M Davis wrote:
>
>> It's most definitely not a bug that IFTI (Implicit Function Template Instantiation) instantiates the template with the exact type that it's given.
>
> The "principle of least astonishment" certainly supports this behavior.  To check my understanding, I forced the type of instantiation:
>
>   writeln(test2!int(v));
>
> and that worked as I expected.
>
> Andy

Yes, it's pretty much the same "solution" but on the caller side instead of the callee. But it works only for types like int or which provides a constructor that generates a mutable copy.
July 26

On Thursday, 25 July 2024 at 13:07:03 UTC, Jonathan M Davis wrote:

>

On Thursday, July 25, 2024 6:00:58 AM MDT Dom DiSc via

>

But a parameter given by value is ALWAYS a copy.

It has to be a full, independent copy. If you're talking about integer types, that's a non-issue, but if you're talking about types with any indirections, it becomes a big issue. If the type contains any pointers, dynamic arrays, class references, etc. then the copy can't be mutable unless you're dealing with a struct with a copy constructor that does a deep copy of all of the member variables. So, in the general case, you cannot copy a value and expect to get a mutable result.

If you are not able to construct a mutable copy of a type, why on earth are you handing it over by value?!?

And what do you do with normal assignment with such a type?

immutable MyNonCopyableType x;
MyNonCopyableType y = x; // compile error?

Also, if you need no writeable copy in your template, why don't you declare it with const parameter?
Maybe you need this behaviour often and so don't want to type the 7 extra characters. But I think, it is a bad default to create const copies of const objects, and to make it worse the language does not allow you to change this default (would need a new keyword "mutable" to explicitly request to get a mutable copy).

> >

Therefore I consider the current behaviour a bug.

It's most definitely not a bug that IFTI (Implicit Function Template Instantiation) instantiates the template with the exact type that it's given. In the general case, that's what you want - particularly since many, many types (probably the vast majority of types) cannot be const or immutable and then result in a mutable copy when they're copied.

If you need this, declare the parameter const.
The default should be a mutable copy (and a compile error if creating a mutable copy is not possible).

>

That really only works with very simple types with no indirections.

Sorry, but no. It is possible to write copy constructors that create mutable copies for pretty much any type. It may for bad designed types be not very fast, but if you are able to create a mutable instance of a type, it should also be possible to create a mutable copy.

>

Now, the fact that code such as
void fun(T : const U, U)(U x) if(is(immutable T == immutable U)) { ... }
or code like
void fun(T)(Unqual!T x) {}
doesn't work with IFTI and requires explicit instantation is definitely a deficiency in IFTI's current capabilities.

So you basically agree that it is a missing feature (if not a bug).

>

we very much want
void fun(T)(T x) {}
to be instantiated as fun!(const Foo) if it's passed a const Foo.

No. this is a bad default. If you want this, write

void fun(T)(const(T) x) {}

I know, it's seven characters more to type, but I think they are definitely worth it.

« First   ‹ Prev
1 2