Bug 9112 – Uniform construction for built-in types
Status
RESOLVED
Resolution
FIXED
Severity
enhancement
Priority
P2
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2012-12-03T20:06:00Z
Last change time
2014-04-03T10:11:17Z
Keywords
pull
Assigned to
nobody
Creator
k.hara.pg
Comments
Comment #0 by k.hara.pg — 2012-12-03T20:06:08Z
Related: http://d.puremagic.com/issues/show_bug.cgi?id=8752
Built-in .init property is useful, but it is sometimes unsafe, and many people might confuse the default initializer and construction.
- Default initializer represents pre-constructing object state.
Users can access to it by using built-in `.init` property.
- Default construction is a default operation which constructing an object
completely.
* For built-in types (int, double, pointers, arrays) and class types,
default construction does nothing. Because, after initializing by
default initializer, the object is already completely constructed.
* For struct types, it is same in basic. After initialized by S.init,
the object is completely constructed. Except:
+ If S is a nested struct, default construction should fill
its frame pointer. So default construction is valid only in
the function where S is defined.
+ If S is a struct which has `@disable this();`, default construction
is disabled explicitly. So default construction is never occur.
Instead, users should call explicitly defined user-defined
constructor which has arguments.
Well, for struct types, default construction has an explicit syntax S().
But, built-in types does not have that. It is inconsistent.
So I'd like to propose new syntax to reduce such concept confusion.
int n = int(); // same as int.init
int n = int(10); // same as cast(int)10;
From the meta-programming view, we can represent default construction of value types by T().
Comment #1 by bearophile_hugs — 2012-12-05T17:31:48Z
This is useful in generic code:
int n = int(10);
So we use:
reduce!q{a + b}(T(0), items)
Instead of:
reduce!q{a + b}(cast(T)0, items)
Comment #2 by issues.dlang — 2012-12-05T18:12:49Z
What I really want to be able to do is stuff like
auto i = new int(5);
auto j = new immutable(int)(7);
Right now, you have to do nonsense like
auto i = new int;
*i = 5;
auto temp = new int;
*temp = 7;
auto j = cast(immutable(int)*)temp;
Pointers to primitive types are just plain ugly to initialize, especially when const and immutable come into play.
Comment #3 by bearophile_hugs — 2012-12-05T18:50:12Z
(In reply to comment #2)
> What I really want to be able to do is stuff like
>
> auto i = new int(5);
Just curious, in what cases do you need that?
Comment #4 by issues.dlang — 2012-12-05T19:04:49Z
> Just curious, in what cases do you need that?
Any time that you want create a pointer to an int - or a float or a bool or any of the primitive types. Right now, you're forced to do two-part initialization instead of initializing it to a value other than init when constructing it, which is particularly bad when const or immutable is involved.
Comment #5 by bearophile_hugs — 2012-12-05T19:37:13Z
(In reply to comment #4)
> Any time that you want create a pointer to an int - or a float or a bool or any
> of the primitive types.
This doesn't answer my question. In what cases do you want this? I think I have never needed this:
auto i = new int(5);
Comment #6 by issues.dlang — 2012-12-05T21:42:28Z
Personally, I've used it so that an integer could be null in cases where I needed to distinguish between having a specific value and having no value at all, but there are plenty of other uses for it. Have you honestly never needed a pointer to an int? It's needed less than some other things to be sure, but there are times when it is needed, and constructing such pointers is horrible with how things stand. It also makes constructing them generically very difficult, which is precisely what Kenji is bringing up in this enhancement request. It's just that he's talking about them when they're on the stack, and I'm pointing out that it would also be valuable to do the same with the heap.
Comment #7 by bearophile_hugs — 2012-12-06T04:37:51Z
(In reply to comment #6)
> Personally, I've used it so that an integer could be null in cases where I
> needed to distinguish between having a specific value and having no value at
> all,
In D std.typecons.Nullable!int is often better than a heap-allocated int.
> Have you honestly never needed a pointer to an int?
Not that I remember.
Comment #8 by k.hara.pg — 2012-12-06T16:55:04Z
I agree with Jonathan. `new immutable int(1)` is also necessary, otherwise we cannot initialize heap-allocated value without type-system hacking (by using cast). I think it is one of hole in current const type system.
Therefore, for all built-in types, both stack allocating syntax (T() and T(v)) and heap allocating syntax (new T() and new T(v)) should be supported. I changed the summary.
But, true uniform construction cannot be implemented currently, because array construction has some special cases.
I've opened a new issue for uniform array construction.
Issue 9120 - Uniform construction for array types
Comment #9 by monarchdodra — 2012-12-06T22:30:28Z
(In reply to comment #1)
> This is useful in generic code:
> int n = int(10);
EXTREMELY useful. Just the other day, I was writing a unittest to cover appender with as many types as possible, and wanted to write something along the lines of:
//----
foreach (SS; TypeTuple!(int, S0, S1, S2, S3, S4, S5, S6, S7))
{
foreach (S; TypeTuple!(SS, const(SS), immutable(SS)))
{
auto app1 = appender!(S[])();
foreach(i; 0 .. 0x20000)
app1.put(S(i)); // HERE
//----
Long story short: "int" didn't make it into the final test...
(In reply to comment #2)
> What I really want to be able to do is stuff like
>
> auto i = new int(5);
> auto j = new immutable(int)(7);
>
> Right now, you have to do nonsense like
>
> auto i = new int;
> *i = 5;
You always have the
auto j = [cast(immutable int)5].ptr;
"Trick". But that's still ugly as sin, and I'm not sure what you are actually paying for when doing this.
Comment #11 by andrej.mitrovich — 2012-12-07T10:39:02Z
(In reply to comment #0)
> So I'd like to propose new syntax to reduce such concept confusion.
>
> int n = int(10); // same as cast(int)10;
So we're introducing C++-style casts into D? This means you can no longer search for 'cast' and expect to find all the unsafe casts in your code. I'm very much against this, and if I remember right Walter was also against this.
> From the meta-programming view, we can represent default construction of value
> types by T().
We can also use a library template to do it without introducing new syntax. If orthogonality is needed because of metaprogramming then why not just implement a template in Phobos?
We really don't need 10 ways of doing the same thing with a different syntax..
Comment #12 by andrej.mitrovich — 2012-12-07T10:41:09Z
(In reply to comment #9)
> (In reply to comment #1)
> > This is useful in generic code:
> > int n = int(10);
>
> EXTREMELY useful. Just the other day, I was writing a unittest to cover
> appender with as many types as possible, and wanted to write something along
> the lines of:
> app1.put(S(i)); // HERE
There:
app1.put(DefInit!S(i));
No need to introduce new syntax because of a small issue like that.
Comment #13 by monarchdodra — 2012-12-07T10:55:13Z
(In reply to comment #11)
> (In reply to comment #0)
> > So I'd like to propose new syntax to reduce such concept confusion.
> >
> > int n = int(10); // same as cast(int)10;
>
> So we're introducing C++-style casts into D? This means you can no longer
> search for 'cast' and expect to find all the unsafe casts in your code. I'm
> very much against this, and if I remember right Walter was also against this.
>
AFAIK, it's not exactly the same thing, as int(10) would be only a constructor, so would not downcast.
Writing:
//----
void foo(long k)
{
int n = int(k);
}
//----
Would fail to compile the same way as:
//----
void foo(long k)
{
int n = k;
}
//----
would. You'd need to use:
//----
void foo(long k)
{
int n = int(cast(int)k);
}
//----
Comment #14 by issues.dlang — 2012-12-07T10:57:47Z
> So we're introducing C++-style casts into D? This means you can no longer
> search for 'cast' and expect to find all the unsafe casts in your code. I'm
> very much against this, and if I remember right Walter was also against this.
I'd actually argue for making it so that no explict casting is involved. It could be restricted to cases where
T var = literal;
and
T var = T(literal);
would be identical. So, stuff like ubyte(12345) would be illegal whereas cast(ubyte)12345 would be legal. It then looks like a C++ cast but isn't. It solves the problem with generic code without causing any issues you get with casts.
> We can also use a library template to do it without introducing new syntax. If
> orthogonality is needed because of metaprogramming then why not just implement
> a template in Phobos?
I tried that. Andrei rejected it, and it's not worth my time to fight him over it. Not to mention, I've always thought that
auto i = new int(7);
auto j = new immutable(int)(5);
should be legal and think that having to do something like makeNew!int(7) is a bit of a hack anyway, since it's specifically working around a deficiency in the language.
> We really don't need 10 ways of doing the same thing with a different syntax..
The whole point of this is to try and make things more consistent, not less.
Comment #15 by issues.dlang — 2012-12-07T10:59:34Z
> AFAIK, it's not exactly the same thing, as int(10) would be only a
> constructor, so would not downcast.
I agree, but if you look at Kenji's proposal, he specifically says that there's a cast involved (which I'd missed when I read it the first time). So, Andrej's complaint is completely valid given Kenji's initial proposal. But if you fix it so that no cast is involved, then I think that it's fine.
Comment #16 by monarchdodra — 2012-12-07T12:20:03Z
(In reply to comment #15)
> > AFAIK, it's not exactly the same thing, as int(10) would be only a
> > constructor, so would not downcast.
>
> I agree, but if you look at Kenji's proposal, he specifically says that there's
> a cast involved (which I'd missed when I read it the first time). So, Andrej's
> complaint is completely valid given Kenji's initial proposal. But if you fix it
> so that no cast is involved, then I think that it's fine.
I'm just wondering if that's *actually* what's going on, or if Kenji just accidentally miss-commented it that way.
I don't know how to read compiler code, so I wouldn't know what he actually did. It'd be nice if he did deliver a fail_compile checking this.
Comment #17 by bearophile_hugs — 2012-12-07T14:33:35Z
(In reply to comment #16)
> (In reply to comment #15)
> > > AFAIK, it's not exactly the same thing, as int(10) would be only a
> > > constructor, so would not downcast.
> >
> > I agree, but if you look at Kenji's proposal, he specifically says that there's
> > a cast involved (which I'd missed when I read it the first time). So, Andrej's
> > complaint is completely valid given Kenji's initial proposal. But if you fix it
> > so that no cast is involved, then I think that it's fine.
>
> I'm just wondering if that's *actually* what's going on, or if Kenji just
> accidentally miss-commented it that way.
>
> I don't know how to read compiler code, so I wouldn't know what he actually
> did. It'd be nice if he did deliver a fail_compile checking this.
You pointed out is correct.
At first, I had considered that int(10) should be translated to cast(int)10. But, while implementing pull, I had discovered the incorrect cast would occur much easily.
So now, the posted pull doesn't implement int(10) as a cast, and the syntax accepts only a value which implicitly convertible to int.
Comment #19 by k.hara.pg — 2012-12-11T05:03:33Z
(In reply to comment #17)
> The pull request was closed hours ago:
> https://github.com/D-Programming-Language/dmd/pull/1356
>
> Do you know where are the comments that have made Hara close it?
Sorry, I had temporarily closed it so auto tester result had been broken in 64bit platforms.
After fixing test cases, I reopened that.
Comment #20 by yebblies — 2013-01-12T23:17:38Z
*** Issue 5347 has been marked as a duplicate of this issue. ***
Comment #21 by andrej.mitrovich — 2013-01-13T07:08:11Z
I just think there's a good chance that the feature could bite us in generic code and leave us spending a lot of time debugging.
Here's a somewhat contrived example:
float sqr(int val)
{
return val * val;
}
template Call(T...)
{
auto Call(int arg)
{
alias T[0] Func;
return Func(arg);
}
}
template GetFunc()
{
alias int GetFunc;
}
void main()
{
alias Call!(GetFunc!()) square;
square(2); // actually calls int(2)
}
test.d(13): Error: function expected before (), not int of type int
With the pull this becomes valid code.
Maybe it's obvious in this case, but when you combine tuples, mixins, etc, it could be hard to notice what's wrong. The way I see it, the more the line between functions and types becomes blurred the harder it can be to track bugs.
Comment #22 by andrej.mitrovich — 2013-02-04T18:06:46Z
*** Issue 2600 has been marked as a duplicate of this issue. ***
Comment #23 by bearophile_hugs — 2013-11-24T05:41:58Z
(In reply to comment #21)
> void main()
> {
> alias Call!(GetFunc!()) square;
> square(2); // actually calls int(2)
> }
>
> test.d(13): Error: function expected before (), not int of type int
>
> With the pull this becomes valid code.
>
> Maybe it's obvious in this case, but when you combine tuples, mixins, etc, it
> could be hard to notice what's wrong. The way I see it, the more the line
> between functions and types becomes blurred the harder it can be to track bugs.
This code gives:
void main() {
cast(int)2;
}
temp.d(2): Error: long has no effect in expression (2)
So I think this will give a similar error message:
void main() {
int(2);
}
On the other hand this probably will not give an error:
...
void main() {
alias Call!(GetFunc!()) square;
auto n = square(2);
}
cast(...) is a dangerous tool, so it's a good idea to introduce a feature like int(...) that avoids some usages of cast(...). T(x) where T is a built-in type is a little safer than a cast because it gives errors when the conversion is not possible.
So I think the advantages here could outweigh the risks.
Comment #24 by github-bugzilla — 2014-03-06T19:47:10Z
Comment #25 by bearophile_hugs — 2014-03-07T02:29:05Z
This is a nice improvement for D.
But there's still some asymmetry between built-in types and user defined ones:
template Foo(alias T) {
alias Foo = T;
}
struct Bar {}
Foo!Bar y1; // OK
Foo!int y2; // error
void main() {}
Comment #26 by bearophile_hugs — 2014-03-07T02:49:15Z
Is this difference expected?
auto identity1(T)(in size_t side) {
auto m = new T[][](side);
foreach (r, ref row; m) {
row.length = side;
foreach (c; 0 .. side)
row[c] = cast(T)(r == c ? 1 : 0);
}
return m;
}
auto identity2(T)(in size_t side) {
auto m = new T[][](side);
foreach (r, ref row; m) {
row.length = side;
foreach (c; 0 .. side)
row[c] = T(r == c ? 1 : 0);
}
return m;
}
void main() {
import std.stdio;
auto m1 = identity1!(int)(3); // OK
m1.writeln;
auto m2 = identity1!(creal)(3); // OK
m2.writeln;
auto m3 = identity2!(int)(3); // OK
m3.writeln;
auto m4 = identity2!(creal)(3); // Error
m4.writeln;
}
Comment #27 by k.hara.pg — 2014-03-07T06:03:52Z
(In reply to comment #26)
> Is this difference expected?
>
[snip]
Yes. The new syntax T(arg) is valid only when the 'arg' is implicitly convertible to T (== different from the cast). And, in your example, int is not implicitly convertible to creal.