Discussion:
How to check if an array is a manifest constant?
simendsjo
2011-04-03 15:29:32 UTC
Permalink
The behavior for manifest constant arrays is different from regular arrays and const/immutable
arrays. The problem is that typeof() returns T[]. How can I see if the array is a manifest
constant?


void g(int[] x) { }

const c = [1,2,3];
static assert(is(typeof(c) == const(int[])));
// cannot convert const(int[]) to int[]
static assert(!__traits(compiles, g(c)));
auto carr = c;
static assert(is(typeof(carr) == const(int[])));
assert(carr.ptr == c.ptr); // referenced

immutable i = [1,2,3];
static assert(is(typeof(i) == immutable(int[])));
// cannot convert immutable(int[]) to int[]
static assert(!__traits(compiles, g(i)));
auto iarr = i;
static assert(is(typeof(iarr) == immutable(int[])));
assert(iarr.ptr == i.ptr); // referenced

enum e = [1,2,3];
// e is reported as int[] even if it's an enum
static assert(is(typeof(e) == int[]));
//static assert(is(typeof(e) == enum)); // not an enum (as expected)
// it can be passed to funtions taking dynamic arrays
void f(int[] x) {
assert(e.ptr != x.ptr); // then the content is copied
}
f(e);
// the behavior is different from other assignments
// as the content is copied
auto earr = e;
assert(earr.ptr != e.ptr); // content is copied
Aleksandar Ružičić
2011-04-03 18:03:35 UTC
Permalink
As far as I understand, manifest constants and enums only share the
same keyword (enum) and are completely different things.
Enumerations (like enum A { a,b, c}) define new type, while manifest
constants (like enum N = 42;) are just constant values with type
inferred from value (unless specified after enum keyword).
Post by simendsjo
The behavior for manifest constant arrays is different from regular arrays and const/immutable
arrays. The problem is that typeof() returns T[]. How can I see if the array is a manifest
constant?
       void g(int[] x) { }
       const c = [1,2,3];
       static assert(is(typeof(c) == const(int[])));
       // cannot convert const(int[]) to int[]
       static assert(!__traits(compiles, g(c)));
       auto carr = c;
       static assert(is(typeof(carr) == const(int[])));
       assert(carr.ptr == c.ptr); // referenced
       immutable i = [1,2,3];
       static assert(is(typeof(i) == immutable(int[])));
       // cannot convert immutable(int[]) to int[]
       static assert(!__traits(compiles, g(i)));
       auto iarr = i;
       static assert(is(typeof(iarr) == immutable(int[])));
       assert(iarr.ptr == i.ptr); // referenced
       enum e = [1,2,3];
       // e is reported as int[] even if it's an enum
       static assert(is(typeof(e) == int[]));
       //static assert(is(typeof(e) == enum)); // not an enum (as expected)
       // it can be passed to funtions taking dynamic arrays
       void f(int[] x) {
               assert(e.ptr != x.ptr); // then the content is copied
       }
       f(e);
       // the behavior is different from other assignments
       // as the content is copied
       auto earr = e;
       assert(earr.ptr != e.ptr); // content is copied
Jonathan M Davis
2011-04-03 23:37:42 UTC
Permalink
Post by Aleksandar Ružičić
As far as I understand, manifest constants and enums only share the
same keyword (enum) and are completely different things.
Enumerations (like enum A { a,b, c}) define new type, while manifest
constants (like enum N = 42;) are just constant values with type
inferred from value (unless specified after enum keyword).
They're the same and they're not. A named enum creates a new type of sorts and
namespaces the values in it, whereas a manifest constant does not create a new
type and its values (be it one or many) go directly in the namespace that it's
declared in. But even a named enum doesn't exactly create a new type. It's
really its base type everywhere it's used, but extra restrictions are put on
it to help ensure that only valid values are used with it. For example, with
std.datetime.Month, you either have to use Month.jan, Month.feb, etc. or cast
an integral value to a Month - e.g. cast(Month)12. However, it's still only a
ubyte when it's compiled in, because Month is an enum of the type ubyte.

So, manifest constants and named enums are not quite the same thing, but
they're not drastically differente either. Their values act essentially the
same way. They're just referenced differently, and you can refer to the name
of a named enum, using it as a type, whereas there is no name for a manifest
constant. Their values are pretty much identical when they're used though.

- Jonathan M Davis
Jonathan M Davis
2011-04-03 23:31:47 UTC
Permalink
Post by simendsjo
The behavior for manifest constant arrays is different from regular arrays
and const/immutable arrays. The problem is that typeof() returns T[]. How
can I see if the array is a manifest constant?
void g(int[] x) { }
const c = [1,2,3];
static assert(is(typeof(c) == const(int[])));
// cannot convert const(int[]) to int[]
static assert(!__traits(compiles, g(c)));
auto carr = c;
static assert(is(typeof(carr) == const(int[])));
assert(carr.ptr == c.ptr); // referenced
immutable i = [1,2,3];
static assert(is(typeof(i) == immutable(int[])));
// cannot convert immutable(int[]) to int[]
static assert(!__traits(compiles, g(i)));
auto iarr = i;
static assert(is(typeof(iarr) == immutable(int[])));
assert(iarr.ptr == i.ptr); // referenced
enum e = [1,2,3];
// e is reported as int[] even if it's an enum
static assert(is(typeof(e) == int[]));
//static assert(is(typeof(e) == enum)); // not an enum (as expected)
// it can be passed to funtions taking dynamic arrays
void f(int[] x) {
assert(e.ptr != x.ptr); // then the content is copied
}
f(e);
// the behavior is different from other assignments
// as the content is copied
auto earr = e;
assert(earr.ptr != e.ptr); // content is copied
There should be _no_ difference between arrays which are manifest constants
and regular arrays which are immutable except that they're computed at compile
time. However, there is currently a bug in CTFE which makes it so that an
array is copied _every_ time that it is accessed in CTFE (which, among other
things, causes a massive memory leak). I don't remember what the bug report is
for it, but Don is currently overhauling CTFE so that this won't be the case
anymore. So, this _should_ be fixed by the next release.

But there should be no need to worry about whether something is a manifest
constant or not. If there is, you're either doing something funny/wrong, or
it's because of a bug like the one that Don is fixing.

- Jonathan M Davis
bearophile
2011-04-04 00:25:25 UTC
Permalink
Post by Jonathan M Davis
But there should be no need to worry about whether something is a manifest
constant or not.
I'd like in DMD a way to know if something is a compile-time constant. There is a GCC extension that sometimes is able to do it, named __builtin_constant_p:
http://www.delorie.com/gnu/docs/gcc/gcc_81.html#IDX629

Bye,
bearophile
Jonathan M Davis
2011-04-04 00:33:23 UTC
Permalink
Post by bearophile
Post by Jonathan M Davis
But there should be no need to worry about whether something is a
manifest constant or not.
I'd like in DMD a way to know if something is a compile-time constant.
There is a GCC extension that sometimes is able to do it, named
http://www.delorie.com/gnu/docs/gcc/gcc_81.html#IDX629
To what end? Why would you care? It matters whether a value is mutable, const,
or immutable, but what does it matter if it was calculated at compile time or
not? Unless it's a property, it's definitely going to have been calculated
before you use it. The only reason that I see that it matters at the moment is
due to bugs in dmd. Once those are fixed, it should be irrelevant.

- Jonathan M Davis
simendsjo
2011-04-04 08:16:50 UTC
Permalink
But there is a difference in how they behave, and I have no way of checking this behavior.
Consider the following little snippet:

void f(int[] a) {
a[0] = -1;
}

void main() {
int[] a = [1,2,3];
static assert(is(typeof(a) == int[]));
f(a);
assert(a[0] == -1); // a passed by reference, a[0] changed

enum b = [1,2,3];
static assert(is(typeof(b) == int[])); // a regular int[] you say?
f(b);
assert(b[0] == -1); // b passed by value, b[0] unchanged
}
Jonathan M Davis
2011-04-04 09:00:20 UTC
Permalink
Post by simendsjo
But there is a difference in how they behave, and I have no way of checking
void f(int[] a) {
a[0] = -1;
}
void main() {
int[] a = [1,2,3];
static assert(is(typeof(a) == int[]));
f(a);
assert(a[0] == -1); // a passed by reference, a[0] changed
enum b = [1,2,3];
static assert(is(typeof(b) == int[])); // a regular int[] you say?
f(b);
assert(b[0] == -1); // b passed by value, b[0] unchanged
}
Okay. I just re-read the appropriate section in TDPL, and it's clear from the
book that it's definitely intended that a new copy of the enum value be
created every time that it's used (I was under the impression that that was
buggy behavior, but apparently that's not true - though the fact that
accessing any array during CTFE results in a copy of that array _is_ buggy
behavior and should be fixed by Don before the next release).

Regardless of that, however, enum values shouldn't be used in any place where
they're going to be altered. So, passing such a value to a function which is
intended to alter the value that it's passed to is just plain a bug on the
part of the caller. It shouldn't matter to the function being called.

That being the case, I don't see why you would need to determine whether a
value that you're dealing with is an enum or not using template constraints or
static if or whatnot. You should know whether the variable that you're using
is an enum or not. If it is, don't alter it until you've set it to a variable
- then you alter the variable.

What use case do you have for wanting to know whether a variable is an enum or
not?

- Jonathan M Davis
simendsjo
2011-04-04 10:11:32 UTC
Permalink
What use case do you have for wanting to know whether a variable is an enum or not?
The same reason I'd like to check if it's const/immutable; if an algorithm requires a
modifiable array.

void sortInPlace(int[] array) {
array.sort;
}

void main() {
int[] a = [3,2,1];
sortInPlace(a);
assert(a == [1,2,3]);

auto i = [3,2,1].idup;
//sortInPlace(i); // Fails at compile time

enum e = [3,2,1];
sortInPlace(e);
assert(e == [1,2,3]); // fails at runtime
}

I can check the type for const or immutable with template constraits or static if's, but I
cannot check for enum.
I can use ref int[] as a parameter to get a compile time error, but that's misleading as I
often don't want to reassign the parameter.

Cannot the type of enum e = [1] be immutable(int[])? Or is that wrong?
Or am I missing something crucial because of CTFE or other stuff? I've just started looking
into D2, so theres a good possibility my reasoning is wrong.
Jonathan M Davis
2011-04-04 10:34:49 UTC
Permalink
Post by simendsjo
What use case do you have for wanting to know whether a variable is an enum or not?
The same reason I'd like to check if it's const/immutable; if an algorithm
requires a modifiable array.
void sortInPlace(int[] array) {
array.sort;
}
void main() {
int[] a = [3,2,1];
sortInPlace(a);
assert(a == [1,2,3]);
auto i = [3,2,1].idup;
//sortInPlace(i); // Fails at compile time
enum e = [3,2,1];
sortInPlace(e);
assert(e == [1,2,3]); // fails at runtime
}
I can check the type for const or immutable with template constraits or
static if's, but I cannot check for enum.
I can use ref int[] as a parameter to get a compile time error, but that's
misleading as I often don't want to reassign the parameter.
Cannot the type of enum e = [1] be immutable(int[])? Or is that wrong?
Or am I missing something crucial because of CTFE or other stuff? I've just
started looking into D2, so theres a good possibility my reasoning is
wrong.
Apparently, part of the point of using enums is to have them be replaced with
the value without having any kind of address. So, they're _supposed_ to be a
copy and therefore don't have to be const or immutable.

Generally, it should be clear whether a function alters its arguments or not.
You can't use const or immutable arrays with a function which alters its array
argument or the elements of its array argument, because the array itself can't
be altered. In the case of enums, it _can_ be altered, because when you use
it, you get a copy. It literally copies and pastes the value of the enum where
you use it. So, there's _nothing_ wrong with passing an enum which is an array
to a function which alters the elements of its array argument. It's perfectly
legal. It's just generally a bad idea, since it's a temporary.

There's no need to check whether a function's argument is an enum anymore than
there's a need to check whether the argument is a temporary. Both are
perfectly legal, just generally pointless. If a slice of the array is returned
or saved in a global variable or whatnot, then it's not pointless. But if it's
just altering the function's argument, then it is. But the function doesn't
need to worry about it. It's still perfectly legal. It's up to the caller to
actually pass an array that's worth altering. And since the function should be
clear on whether it alters its arguments, it shouldn't be a problem that it's
up to the caller to use it correctly with regards to temporaries and enums -
just like it's up to the caller not to be stupid and pass any other array that
it doesn't actually want to alter.

So, really there's no reason to stop an enum from working with a function that
alters the elements of an array. All temporaries have the same issue. If you
really want to prevent it, then just make the argument a ref. It also makes it
clear that you're altering the array. If you don't want to make it a ref, then
just make the documentation clear, and it's up to the caller to use the
function appropriately. For instance, it should be obvious to the caller that
passing an enum to a function like sortInPlace would be just stupid.

- Jonathan M Davis

Loading...