Jump to page: 1 2
Thread overview
December 12

Since ceasing my use of D out of concern for the direction and health of the project, I've nonetheless been following these forums and have seen what I consider to be sincere efforts to improve things.

So I've started doing a bit of D work again and ran into this:

import std.stdio;

struct Foo {
    int bar;
    int baz;
}

void bletch(alias field)(Foo s, int x) {
	s.field = x;
	writeln("%d", s.field);
}

int main(string[] args)
{
    Foo s;
    bletch!bar(s);
    bletch!baz(s);
    return 0;
}

Compiling with dmd 2.106.0 on an up-to-date NixOS system results in

(dmd-2.106.0)dca@giovanni:/home/dca/Software/d_tests$ dmd test.d
test.d(16): Error: undefined identifier `bar`
test.d(17): Error: undefined identifier `baz`

So it appears that symbols passed to a function template alias parameter need to be in scope at the instantiation site as opposed to within the instantiated code.

This makes no sense to me. The symbols at the instantiation site are not being evaluated in that scope. They will be evaluated by the compiler when it gets its hands on the instantiated code. If a simple compile-time substitution were done in the above code, those symbols would both be defined in the instantiated code.

Reading the documentation, I'm guessing that template mixins can be used to address this problem, so it's probably the case (I haven't tried it) that I can achieve what I want that way.

But I'm failing to understand why the problem exists in the first place. Perhaps inherited from C++, which I do not know, having had some early exposure to it and concluded it was awful?

I'm probably missing something here, perhaps some important cases where the behavior I'm seeing does make sense. I'd be interested in the comments of those who know D (and perhaps C++) better than I do.

December 12

On Tuesday, 12 December 2023 at 19:02:11 UTC, Don Allen wrote:

>

I'm probably missing something here, perhaps some important cases where the behavior I'm seeing does make sense. I'd be interested in the comments of those who know D (and perhaps C++) better than I do.

Your imagining aliases as words rather than references to definitions

As far as I know, aliases are optimized to work with "overload sets"

void foo(int);
void foo(bool);

template bar(alias A){}
bar!foo;

A is now all foo's, foo's need to exist, and foo mostly pretends to stop knowing where it comes from

do:

struct Foo {
    int bar;
    int baz;
}

void bletch(alias field)(int x) {
	field = x;
	writeln("%d", s.field);
}

int main(string[] args)
{
    Foo s;
    bletch!(s.bar);
    bletch!(s.baz);
    return 0;
}
December 12
On 12/12/23 20:02, Don Allen wrote:
> Since ceasing my use of D out of concern for the direction and health of the project, I've nonetheless been following these forums and have seen what I consider to be sincere efforts to improve things.
> 
> So I've started doing a bit of D work again and ran into this:
> 
> ````
> import std.stdio;
> 
> struct Foo {
>      int bar;
>      int baz;
> }
> ...

This defines three symbols:

Foo
Foo.bar
Foo.baz

There is however no `bar` and no `baz` at module scope.


> void bletch(alias field)(Foo s, int x) {
>      s.field = x;
>      writeln("%d", s.field);
> }
> ...

This attempts to access `s.field`, but `field` is not a member of `Foo`.


> int main(string[] args)
> {
>      Foo s;
>      bletch!bar(s);
>      bletch!baz(s);
>      return 0;
> }
> ````
> ...

This find neither `bar` nor `baz`, because those are nested in `Foo`. There are symbols `Foo.bar` and `Foo.baz`, but not `bar` and `baz`.


The idiomatic way to achieve what you want is this:

```d
import std.stdio;

struct Foo{
    int bar;
    int baz;
}

void bletch(string field)(Foo s, int x){
    __traits(getMember, s, field) = x;
    writeln(__traits(getMember, s, field));
}

void main(){
    Foo s;
    bletch!"bar"(s, 1);
    bletch!"baz"(s, 2);
}
```

Arguably this is not very nice, but it does the job.

You can pass `Foo.bar` and `Foo.baz` as aliases, but I think currently there is no good way to access a field of an object based on an alias to the field symbol. (Other than `__traits(getMember, self, __traits(identifier, field))`, but that bypasses all the typechecking you might get anyway.)


December 13
On Tuesday, 12 December 2023 at 21:48:59 UTC, Timon Gehr wrote:
> 
> The idiomatic way to achieve what you want is this:
>     __traits(getMember, s, field) = x;

No that will break eventually because strings are not reliable references; also __traits are ugly and for other abstractions
December 13

On Tuesday, 12 December 2023 at 19:50:25 UTC, monkyyy wrote:

>

On Tuesday, 12 December 2023 at 19:02:11 UTC, Don Allen wrote:

>

I'm probably missing something here, perhaps some important cases where the behavior I'm seeing does make sense. I'd be interested in the comments of those who know D (and perhaps C++) better than I do.

Your imagining aliases as words rather than references to definitions

As far as I know, aliases are optimized to work with "overload sets"

void foo(int);
void foo(bool);

template bar(alias A){}
bar!foo;

A is now all foo's, foo's need to exist, and foo mostly pretends to stop knowing where it comes from

do:

struct Foo {
    int bar;
    int baz;
}

void bletch(alias field)(int x) {
	field = x;
	writeln("%d", s.field);
}

int main(string[] args)
{
    Foo s;
    bletch!(s.bar);
    bletch!(s.baz);
    return 0;
}

I regard templates as a form of more hygienic macros than simple text substitution. To me, they are a form of meta-programming -- code writing code. My view may be wrong and if so, I'd like to know why. If not, I have not seen an answer that explains why a symbol passed to an alias parameter needs a definition in the scope of the template instantiation, as opposed to within the instantiated template body.

What you have suggested above is a way to make my little example work. While less interested in that, I'm not un-interested :-) So I played with this a bit.

Taking your approach, cleaned up a bit (the bletch calls need the int argument), I tried

import std.stdio;

struct Foo {
    int bar;
    int baz;
}

void bletch(alias field)(int n) {
	field = n;
	writeln("%d", field);
}

int main(string[] args)
{
    Foo s;
    bletch!(s.bar)(5);
    bletch!(s.baz)(6);
    return 0;
}

This results in

[nix-shell:~/Software/d_tests]$ dmd test.d
test.d(16): Error: calling non-static function `bletch` requires an instance of type `Foo`
test.d(17): Error: calling non-static function `bletch` requires an instance of type `Foo`
(dmd-2.106.0)

Since field is aliased to s.bar or s.baz, I tried this:

import std.stdio;

struct Foo {
    int bar;
    int baz;
}

void bletch(alias field)(Foo s, int n) {
	field = n;
	writeln("%d", field);
}

int main(string[] args)
{
    Foo s;
    bletch!(s.bar)(s, 5);
    bletch!(s.baz)(s, 6);
    return 0;
}

Same complaints from the compiler:

[nix-shell:~/Software/d_tests]$ dmd test.d
test.d(16): Error: calling non-static function `bletch` requires an instance of type `Foo`
test.d(17): Error: calling non-static function `bletch` requires an instance of type `Foo`
(dmd-2.106.0)
December 13
On Tuesday, 12 December 2023 at 21:48:59 UTC, Timon Gehr wrote:

> You can pass `Foo.bar` and `Foo.baz` as aliases, but I think currently there is no good way to access a field of an object based on an alias to the field symbol. (Other than `__traits(getMember, self, __traits(identifier, field))`, but that bypasses all the typechecking you might get anyway.)

`__traits(child, ...)` works sometimes.


December 13

On Wednesday, 13 December 2023 at 03:37:25 UTC, Don Allen wrote:

>

Same complaints from the compiler:

This works (the explicit template is due to a 'need this' bug):

import std.stdio;

struct Foo {
    int bar;
    int baz;
}

template bletch(alias field)
{
    static void bletch(T)(ref T self, int x) {
        auto pField = &__traits(child, self, field);
        *pField = x;
        writefln("%d", *pField);
    }
}

int main(string[] args)
{
    Foo s;
    bletch!(s.bar)(s, 1);
    bletch!(s.baz)(s, 2);
    return 0;
}
December 12
On 12/12/2023 11:02 AM, Don Allen wrote:
> Since ceasing my use of D out of concern for the direction and health of the project, I've nonetheless been following these forums and have seen what I consider to be sincere efforts to improve things.

Welcome back!


> So I've started doing a bit of D work again and ran into this:
> 
> ````
> import std.stdio;
> 
> struct Foo {
>      int bar;
>      int baz;
> }
> 
> void bletch(alias field)(Foo s, int x) {
>      s.field = x;
>      writeln("%d", s.field);
> }
> 
> int main(string[] args)
> {
>      Foo s;
>      bletch!bar(s); // undefined identifier `bar`

bar is looked up in this scope, meaning it is looked up in main(), and then looked up in the module. It is not found, because bar is in the scope of Foo. Hence the error message for this line

>      bletch!baz(s);
>      return 0;
> }
> ````
> But I'm failing to understand why the problem exists in the first place.

The misunderstanding appears to be the assumption that `field` is resolved inside the definition of `bletch`. It is not, it is resolved at the point of instantiation of the template `bletch`.

> Perhaps inherited from C++, which I do not know, having had some early exposure to it and concluded it was awful?

C++ has Argument Dependent Lookup (ADL) which follows different rules that I have largely forgotten, despite having implemented them in my C++ compiler. ADL was invented by Andrew Koenig to get around lookup problems with operator overloading where `this` was on the right hand side of the operator. I never liked ADL, it is confusing and a giant ugly kludge. I know of no other language that has an equivalent.

December 12
On 12/12/2023 7:37 PM, Don Allen wrote:
> ````
> import std.stdio;
> 
> struct Foo {
>      int bar;
>      int baz;
> }
> 
> void bletch(alias field)(Foo s, int n) {
>      field = n;
>      writeln("%d", field);
> }
> 
> int main(string[] args)
> {
>      Foo s;
>      bletch!(s.bar)(s, 5);
>      bletch!(s.baz)(s, 6);
>      return 0;
> }
> 
> ````
> 
> Same complaints from the compiler:
> ````
> [nix-shell:~/Software/d_tests]$ dmd test.d
> test.d(16): Error: calling non-static function `bletch` requires an instance of type `Foo`
> test.d(17): Error: calling non-static function `bletch` requires an instance of type `Foo`
> (dmd-2.106.0)
> ````

The trouble here is that `s` is a local variable of `main`. The `bletch!(s.bar)` is passing `s` not by reference, and not by value, but by symbol. Inside the body of `bletch`, there is no way that it can get at the runtime value of the symbol `main.s`. Hence, the complaint that `bletch` requires a way to get at the instance of `main.s`.

D has 3 ways of passing arguments:

1. by value
2. by pointer (aka by reference)
3. by symbol (aka by alias)

Passing by symbol is strictly a compile time phenomenon, it cannot be a runtime one.

C++'s method of passing by alias is called a "template template parameter", and is limited to passing the names of template symbols. D generalized it so any symbol could be passed that way.

C macros can have arguments, and they are always passed by name, which is roughly similar to by alias. They are also a compile-time phenomenon, not a runtime one.
December 13

On Wednesday, 13 December 2023 at 03:37:25 UTC, Don Allen wrote:

>

On Tuesday, 12 December 2023 at 19:50:25 UTC, monkyyy wrote:

>

On Tuesday, 12 December 2023 at 19:02:11 UTC, Don Allen wrote:

>

[..]

Try this, it works since dmd 2.087:

struct Foo {
    int bar;
    double baz;

	void bletch(alias field)(typeof(field) newValue) {
        field = newValue;
	}
}

void main(string[] args) {
    import std.stdio;

    Foo s;
    s.bletch!(Foo.bar)(3);
    s.bletch!(s.baz)(4); // this also works - the same as above
    writeln(s);

    auto setBar = &s.bletch!(Foo.bar);
    auto setBaz = &s.bletch!(Foo.baz);

    pragma (msg, typeof(setBar));
    pragma (msg, typeof(setBaz));
    setBar(18);
    setBaz(19.5);
    writeln(s);
}

By making bletch a member function of Foo it will naturally have "an instance of type Foo" - the implicit this parameter that all member functions receive. Afterwards, you can see that we can take the address of the member function template instance &s.bletch!(Foo.bar) which results in a delegate (and not a function pointer). This allows us to call bletch through this delegate as it will supply s as the context pointer.

« First   ‹ Prev
1 2