www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - compile time 'address'

reply Dominic Jones <dominic.jones gmx.co.uk> writes:
Hello,

I would like to capture the "address" of variables at compile 
time. Something not far from this can be done in C++, namely 
addresses can be compared but not captured (then compared, or 
whatever).

I was hoping that it could be done in D, so I wrote something 
similar to test. It seems that D is more restrictive than C++ in 
this matter, as even comparison cannot be performed at compile 
time, as far as I can tell.

What I wish to do may in principle not be possible, but I am not 
familiar enough with compilers to know for sure. Also, I am not 
so familiar with D, so a better transliteration from the C++ 
example maybe possible and yield something on par or better.

The reason for wanting to do all this is to find some way of 
'baking in' the name of a variable (or some locally unique 
identifier) into an expression tree node type, facilitating 
certain expression tree transforms.

The closest I can get to what I want makes use of mixins, but is 
not ideal (https://run.dlang.io/is/Fr2b3f).


C++ (at: https://godbolt.org/z/CLPzGq)

template<typename T, typename U>
auto constexpr cmp(T const &t, U const &u)
{
/* Preferable, but not possible:
      auto constexpr ta = &t; // capture
      auto constexpr ua = &u;
      return ta == ua;
*/
   return &t == &u;
}

int main()
{
   auto c0 = 1;
   auto c1 = 2;

   static_assert(cmp(c0, c0));
   static_assert(!cmp(c0, c1));
}


D (at: https://run.dlang.io/is/rf5azp)

auto cmp(T, U)(const ref T t, const ref U u)
{
   return &t == &u;
}

void main()
{
   auto c0 = 1;
   auto c1 = 2;

   // error: "variable c0 cannot be read at compile time"
   static assert(cmp(c0, c0));
   static assert(!cmp(c0, c1));
}
Nov 29 2018
next sibling parent bauss <jj_1337 live.dk> writes:
On Thursday, 29 November 2018 at 15:56:54 UTC, Dominic Jones 
wrote:
 Hello,

 I would like to capture the "address" of variables at compile 
 time. Something not far from this can be done in C++, namely 
 addresses can be compared but not captured (then compared, or 
 whatever).

 I was hoping that it could be done in D, so I wrote something 
 similar to test. It seems that D is more restrictive than C++ 
 in this matter, as even comparison cannot be performed at 
 compile time, as far as I can tell.

 What I wish to do may in principle not be possible, but I am 
 not familiar enough with compilers to know for sure. Also, I am 
 not so familiar with D, so a better transliteration from the 
 C++ example maybe possible and yield something on par or better.

 The reason for wanting to do all this is to find some way of 
 'baking in' the name of a variable (or some locally unique 
 identifier) into an expression tree node type, facilitating 
 certain expression tree transforms.

 The closest I can get to what I want makes use of mixins, but 
 is not ideal (https://run.dlang.io/is/Fr2b3f).


 C++ (at: https://godbolt.org/z/CLPzGq)

 template<typename T, typename U>
 auto constexpr cmp(T const &t, U const &u)
 {
 /* Preferable, but not possible:
      auto constexpr ta = &t; // capture
      auto constexpr ua = &u;
      return ta == ua;
 */
   return &t == &u;
 }

 int main()
 {
   auto c0 = 1;
   auto c1 = 2;

   static_assert(cmp(c0, c0));
   static_assert(!cmp(c0, c1));
 }


 D (at: https://run.dlang.io/is/rf5azp)

 auto cmp(T, U)(const ref T t, const ref U u)
 {
   return &t == &u;
 }

 void main()
 {
   auto c0 = 1;
   auto c1 = 2;

   // error: "variable c0 cannot be read at compile time"
   static assert(cmp(c0, c0));
   static assert(!cmp(c0, c1));
 }
Use enum instead of auto.
Nov 29 2018
prev sibling next sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 11/29/18 10:56 AM, Dominic Jones wrote:
 Hello,
 
 I would like to capture the "address" of variables at compile time. 
 Something not far from this can be done in C++, namely addresses can be 
 compared but not captured (then compared, or whatever).
 
 I was hoping that it could be done in D, so I wrote something similar to 
 test. It seems that D is more restrictive than C++ in this matter, as 
 even comparison cannot be performed at compile time, as far as I can tell.
 
 What I wish to do may in principle not be possible, but I am not 
 familiar enough with compilers to know for sure. Also, I am not so 
 familiar with D, so a better transliteration from the C++ example maybe 
 possible and yield something on par or better.
 
 The reason for wanting to do all this is to find some way of 'baking in' 
 the name of a variable (or some locally unique identifier) into an 
 expression tree node type, facilitating certain expression tree transforms.
 
 The closest I can get to what I want makes use of mixins, but is not 
 ideal (https://run.dlang.io/is/Fr2b3f).
 
 
 C++ (at: https://godbolt.org/z/CLPzGq)
 
 template<typename T, typename U>
 auto constexpr cmp(T const &t, U const &u)
 {
 /* Preferable, but not possible:
       auto constexpr ta = &t; // capture
       auto constexpr ua = &u;
       return ta == ua;
 */
    return &t == &u;
 }
 
 int main()
 {
    auto c0 = 1;
    auto c1 = 2;
 
    static_assert(cmp(c0, c0));
    static_assert(!cmp(c0, c1));
 }
 
 
 D (at: https://run.dlang.io/is/rf5azp)
 
 auto cmp(T, U)(const ref T t, const ref U u)
 {
    return &t == &u;
 }
 
 void main()
 {
    auto c0 = 1;
    auto c1 = 2;
 
    // error: "variable c0 cannot be read at compile time"
    static assert(cmp(c0, c0));
    static assert(!cmp(c0, c1));
 }
So this is a combination of 2 problems. 1. No you cannot read c0 or c1 at compile time, because they are runtime values. You can fix this by changing them to immutable or enum. 2. D doesn't like you taking the address of compile-time values, but the error message is really bad. If I do this: auto cmp(T, U)(const T t, const U u) // note, no ref { return t == u; // note, change to value not address } void main() { immutable c0 = 1; // note change to immutable immutable c1 = 2; // these now work. static assert(cmp(c0, c0)); static assert(!cmp(c0, c1)); } If I don't change away from ref, it complains that it can't read c0 or c1 at compile time, which clearly is shown to be readable at compile time. I think the error message should be more like "Cannot use reference to this variable at compile-time". -Steve
Nov 29 2018
parent reply Dominic Jones <dominic.jones gmx.co.uk> writes:
Hello Steve,

Thank you for your comments. A couple of replies:


On Thursday, 29 November 2018 at 16:29:00 UTC, Steven 
Schveighoffer wrote:
 1. No you cannot read c0 or c1 at compile time, because they 
 are runtime values. You can fix this by changing them to 
 immutable or enum.
The variables are deliberately 'auto' rather than 'auto constexpr' as per the C++ example, so I do wish to preserve that in the D example. Nevertheless, in C++ querying (at least to perform comparison) the sudo-address, whatever it is (perhaps, behind the scenes it is simply a stack frame offset), is permitted.
 2. D doesn't like you taking the address of compile-time 
 values, but the error message is really bad.

 If I do this:

 auto cmp(T, U)(const T t, const U u) // note, no ref
 {
   return t == u; // note, change to value not address
 }
Presumably, this is now simply comparing values held by the variables, rather than the sudo-address of the original variables as per the C++ example?
Nov 29 2018
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 11/29/18 12:59 PM, Dominic Jones wrote:
 Hello Steve,
 
 Thank you for your comments. A couple of replies:
 
 
 On Thursday, 29 November 2018 at 16:29:00 UTC, Steven Schveighoffer wrote:
 1. No you cannot read c0 or c1 at compile time, because they are 
 runtime values. You can fix this by changing them to immutable or enum.
The variables are deliberately 'auto' rather than 'auto constexpr' as per the C++ example, so I do wish to preserve that in the D example. Nevertheless, in C++ querying (at least to perform comparison) the sudo-address, whatever it is (perhaps, behind the scenes it is simply a stack frame offset), is permitted.
 2. D doesn't like you taking the address of compile-time values, but 
 the error message is really bad.

 If I do this:

 auto cmp(T, U)(const T t, const U u) // note, no ref
 {
   return t == u; // note, change to value not address
 }
Presumably, this is now simply comparing values held by the variables, rather than the sudo-address of the original variables as per the C++ example?
Ah, ok. Essentially you are looking for the layout of the local variables. One thing that is available for D is the compile-time property offsetof, which gives the offset from the start of an aggregate that an item is. for example: struct S { int x; bool y; } static assert(S.y.offsetof == 4); So while this isn't available for function locals, the idea is similar. But I don't think there's an equivalent to what you are able to do here in C++. I don't think it would be a huge stretch to apply this same property to function locals, but it would require an enhancement request. -Steve
Nov 29 2018
next sibling parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Thursday, 29 November 2018 at 18:49:27 UTC, Steven 
Schveighoffer wrote:

 I don't think it would be a huge stretch to apply this same 
 property to function locals, but it would require an 
 enhancement request.

 -Steve
This compiles since 2.069.2 (according to run.dlang.io): void main() { auto c0 = 1; auto c1 = 2; static assert(&c0 == &c0); static assert(&c0 != &c1); } I wonder if that is a bug, or that the original question not compiling is :D
Nov 29 2018
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 11/29/18 2:16 PM, Stanislav Blinov wrote:
 On Thursday, 29 November 2018 at 18:49:27 UTC, Steven Schveighoffer wrote:
 
 I don't think it would be a huge stretch to apply this same property 
 to function locals, but it would require an enhancement request.
This compiles since 2.069.2 (according to run.dlang.io): void main() {     auto c0 = 1;     auto c1 = 2;     static assert(&c0 == &c0);     static assert(&c0 != &c1); } I wonder if that is a bug, or that the original question not compiling is :D
I think the issue is that D is expecting you to be able to use the value pointed at possibly in the function, which would normally be fully available at CTFE (the two variables are runtime variables). There's no way to accept a pointer that can't be used, just the value of the pointer checked. I'm not sure how it would work exactly. I suppose you could accept void *, I don't know if it's possible to recast in CTFE? -Steve
Nov 29 2018
parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Thursday, 29 November 2018 at 20:22:16 UTC, Steven 
Schveighoffer wrote:

 This compiles since 2.069.2 (according to run.dlang.io):
 
 void main() {
      auto c0 = 1;
      auto c1 = 2;
      static assert(&c0 == &c0);
      static assert(&c0 != &c1);
 }
 
 I wonder if that is a bug, or that the original question not 
 compiling is :D
I think the issue is that D is expecting you to be able to use the value pointed at possibly in the function, which would normally be fully available at CTFE (the two variables are runtime variables). There's no way to accept a pointer that can't be used, just the value of the pointer checked.
Yeah, but this one seems a bit deliberate. The asserts hold, but you can't e.g. enum diff = &c0 - &c1; // 'c0' can't be used at compile time
 I'm not sure how it would work exactly. I suppose you could 
 accept void *, I don't know if it's possible to recast in CTFE?
It isn't.
Nov 29 2018
next sibling parent Alex <sascha.orlov gmail.com> writes:
On Thursday, 29 November 2018 at 21:00:57 UTC, Stanislav Blinov 
wrote:
 Yeah, but this one seems a bit deliberate. The asserts hold, 
 but you can't e.g.

 enum diff = &c0 - &c1; // 'c0' can't be used at compile time
What would you say to this: https://forum.dlang.org/thread/tdmkajpnqkbywdnzddqc forum.dlang.org
Nov 29 2018
prev sibling parent Dominic Jones <dominic.jones gmx.co.uk> writes:
On Thursday, 29 November 2018 at 21:00:57 UTC, Stanislav Blinov 
wrote:

 There's no way to accept a pointer that can't be used, just 
 the value of the pointer checked.
Yeah, but this one seems a bit deliberate. The asserts hold, but you can't e.g. enum diff = &c0 - &c1; // 'c0' can't be used at compile time
This is the same situation as C++; the addresses can only be compared (with == or !=). Any attempt to capture their values first will cause a compilation failure, i.e. attempting: // "error: '& t' is not a constant expression", etc auto constexpr ta = &t; auto constexpr ua = &u; return ta == ua;
Nov 29 2018
prev sibling parent reply Dominic Jones <dominic.jones gmx.co.uk> writes:
On Thursday, 29 November 2018 at 18:49:27 UTC, Steven 
Schveighoffer wrote:

 Ah, ok. Essentially you are looking for the layout of the local 
 variables.

 One thing that is available for D is the compile-time property 
 offsetof, which gives the offset from the start of an aggregate 
 that an item is.

 for example:

 struct S
 {
   int x;
   bool y;
 }

 static assert(S.y.offsetof == 4);

 So while this isn't available for function locals, the idea is 
 similar. But I don't think there's an equivalent to what you 
 are able to do here in C++.

 I don't think it would be a huge stretch to apply this same 
 property to function locals, but it would require an 
 enhancement request.
This is an interesting idea. Somehow it would be helpful to access the frame pointer, too, from the variable. Obtaining this offset via the variable would enable disambiguation between variables with the same local offset but from different functions. The example would then read something like: auto cmp(T, U)(const ref T t, const ref U u) { // i.e. // t.frame.offsetof := &main; // t.offsetof := main.c0.offsetof; enum f = t.frame.offsetof == u.frame.offsetof; enum v = t.offsetof == u.offsetof; return f && v; } void main() { auto c0 = 1; auto c1 = 2; static assert(cmp(c0, c0)); static assert(!cmp(c0, c1)); }
Nov 29 2018
parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 11/29/18 5:56 PM, Dominic Jones wrote:
 On Thursday, 29 November 2018 at 18:49:27 UTC, Steven Schveighoffer wrote:
 
 Ah, ok. Essentially you are looking for the layout of the local 
 variables.

 One thing that is available for D is the compile-time property 
 offsetof, which gives the offset from the start of an aggregate that 
 an item is.

 for example:

 struct S
 {
   int x;
   bool y;
 }

 static assert(S.y.offsetof == 4);

 So while this isn't available for function locals, the idea is 
 similar. But I don't think there's an equivalent to what you are able 
 to do here in C++.

 I don't think it would be a huge stretch to apply this same property 
 to function locals, but it would require an enhancement request.
This is an interesting idea. Somehow it would be helpful to access the frame pointer, too, from the variable. Obtaining this offset via the variable would enable disambiguation between variables with the same local offset but from different functions.
Hm... the offsetof feature would break down there. But perhaps a frame (or probably __traits call) could provide the function symbol, so you could do: static assert(t.frame is u.frame); Strawman could be __traits(getAggregate, sym) => symbol that contains sym, be it a function or struct or whatever.
 
 
 The example would then read something like:
 
 auto cmp(T, U)(const ref T t, const ref U u)
 {
    // i.e.
    //   t.frame.offsetof := &main;
    //   t.offsetof := main.c0.offsetof;
 
    enum f = t.frame.offsetof == u.frame.offsetof;
    enum v = t.offsetof == u.offsetof;
    return f && v;
 }
 
 void main()
 {
    auto c0 = 1;
    auto c1 = 2;
 
    static assert(cmp(c0, c0));
    static assert(!cmp(c0, c1));
 }
 
I was thinking you'd do it with D compile time features: bool cmp(alias var1, alias var2) = var1.offsetof == var2.offsetof; struct S { int x; int y; } // this actually works today: static assert(cmp!(S.x, S.x)); static assert(!cmp!(S.x, S.y)); void main() { auto c0 = 1; auto c1 = 2; // proposed static assert(cmp!(c0, c0)); static assert(!cmp!(c0, c1)); } -Steve
Nov 29 2018
prev sibling parent reply aliak <something something.com> writes:
On Thursday, 29 November 2018 at 15:56:54 UTC, Dominic Jones 
wrote:
 Hello,

 I would like to capture the "address" of variables at compile 
 time. Something not far from this can be done in C++, namely 
 addresses can be compared but not captured (then compared, or 
 whatever).

 [...]
I think you can do that by using aliases. This seems to work: auto cmp(alias t, alias u)() { return &t == &u; } void main() { auto c0 = 1; auto c1 = 2; static assert(cmp!(c0, c0)); static assert(!cmp!(c0, c1)); } Cheers, - Ali
Nov 29 2018
next sibling parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 11/29/18 7:23 PM, aliak wrote:
 On Thursday, 29 November 2018 at 15:56:54 UTC, Dominic Jones wrote:
 Hello,

 I would like to capture the "address" of variables at compile time. 
 Something not far from this can be done in C++, namely addresses can 
 be compared but not captured (then compared, or whatever).

 [...]
I think you can do that by using aliases. This seems to work: auto cmp(alias t, alias u)() {   return &t == &u; } void main() {   auto c0 = 1;   auto c1 = 2;   static assert(cmp!(c0, c0));   static assert(!cmp!(c0, c1)); }
Sweet :) wish I had thought of that earlier... -Steve
Nov 29 2018
prev sibling parent Dominic Jones <dominic.jones gmx.co.uk> writes:
On Friday, 30 November 2018 at 00:23:08 UTC, aliak wrote:


 I think you can do that by using aliases. This seems to work:

 auto cmp(alias t, alias u)() {
   return &t == &u;
 }

 void main() {
   auto c0 = 1;
   auto c1 = 2;
   static assert(cmp!(c0, c0));
   static assert(!cmp!(c0, c1));
 }
Unfortunately, with the solution above, the variables are passed as template arguments. Somehow, I need to have the variables being passed as regular arguments since my intended use case is operator overloading for expression trees. Consider the example, below: somehow, I need to be able to know the nominal addresses of parameters passed into 'opBinary' so that the 'Binary' template may be defined with address tags which relate to the operands of 'opBinary'. If the types and tags of the two variables match then there may be a guarantee (at the file scope) that the arguments are indeed the same. struct Terminal { auto opBinary(string op, R)(const ref R r) { enum l_adr = 0; // proposed: this.scope.offsetof + this.offsetof; enum r_adr = 4; // proposed: r.scope.offsetof + r.offsetof; return Binary!(op, typeof(this), R, l_adr, r_adr)(this, r); } } struct Binary(string op, L, R, int l_adr, int r_adr) { L l; R r; } void main() { Terminal c0; Terminal c1; pragma(msg, typeof(c0 + c1)); // 'Binary!("+", Terminal, Terminal, 0, 4)' // pragma(msg, typeof(transform(c0 + c0))); // becomes: "2 * c0" // pragma(msg, typeof(transform(c0 + c1))); // becomes: "c0 + c1" } (at: https://run.dlang.io/is/hwVI5n) -Dominic
Nov 30 2018