www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Constructor Parameter vs Member Variable

reply Christopher Winter <christopher.winter ahrefs.com> writes:
If you have a struct member variable named `a` and a constructor 
for that struct that takes a parameter named `a`, it isn't 
obvious what the usage of `a` in the body refers to.

```
import std;

struct F
{
     int a;
     int b;

     this(double a)
     {
         writeln(a);
         a = a;
     }
}

void main()
{
     scope f = F(15);

     writeln(f.a);
}
```

The behavior is extremely unintutive, and arguably broken, in 
that it a) compiles and b) will print 15 on the first line and 0 
on the second line. Additionally, if you rename the constructor 
parameter to be `c`, then it fails to compile (because you can't 
assign a double to an int).

Is there a way to have the compiler warn or error in this 
situation? Or in general does anyone know ways to avoid the 
ambiguity, other than just being careful with names?
Dec 04 2023
parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Monday, December 4, 2023 7:41:51 PM MST Christopher Winter via Digitalmars-
d wrote:
 If you have a struct member variable named `a` and a constructor
 for that struct that takes a parameter named `a`, it isn't
 obvious what the usage of `a` in the body refers to.

 ```
 import std;

 struct F
 {
      int a;
      int b;

      this(double a)
      {
          writeln(a);
          a = a;
      }
 }

 void main()
 {
      scope f = F(15);

      writeln(f.a);
 }
 ```

 The behavior is extremely unintutive, and arguably broken, in
 that it a) compiles and b) will print 15 on the first line and 0
 on the second line. Additionally, if you rename the constructor
 parameter to be `c`, then it fails to compile (because you can't
 assign a double to an int).

 Is there a way to have the compiler warn or error in this
 situation? Or in general does anyone know ways to avoid the
 ambiguity, other than just being careful with names?
This behavior is actually extremely common in OO programming languages. The solution to this is to use the this reference when there's any ambiguity. The more local symbol is always going to win when one symbol shadows another, and while some kinds of symbol shadowing are disallowed in D, completely disallowing it can be annoying (e.g. plenty of programmers would be annoyed if they had to name constructor parameters differently from member variables when the whole point of the parameter is to assign to the member variable). So, in your example, you'd do this(double a) { this.a = cast(int) a; } Of course, it's complicated by the fact that you made the parameter double, and the member variable int, whereas usually, the types would match (so the cast wouldn't be required), but in general, it's extremely common (both in D and other OO languages such as C++, Java, etc.) to name parameters to a constructor with the exact same name as the member variables that they're intended to be assigned to. Now, the other solution to this is to name member variables differently from other variables, and when they're private, this is extremely common practice, in which case, just looking at the variable name makes it's clear that you're looking at a private member variable. The most common naming schemes for such member variables would be to prepend them with an underscore or to prepend them with m_. e.g. struct S { private int _a; this(int a) { _a = a; } } But that sort of thing isn't normally done with public members. - Jonathan M Davis
Dec 04 2023
parent reply Christopher Winter <christopher.winter ahrefs.com> writes:
On Tuesday, 5 December 2023 at 04:31:28 UTC, Jonathan M Davis 
wrote:
 This behavior is actually extremely common in OO programming 
 languages.
Yeah, I understand that it is variable shadowing, but this nearly identical situation is rejected by the compiler: ``` void fun(double a) { int a = cast(int) a; <-- Error: variable `a` is shadowing variable `main.fun.a` } ``` I don't really understand why these scenarios would be treated differently by the compiler (I would even argue that my intention here is more explicit vs in the constructor, but not everyone may agree with that).
 The solution to this is to use the this reference when there's 
 any ambiguity.
Yeah, I suppose for me always using `this` is probably the best way to avoid any ambiguity.
Dec 05 2023
next sibling parent DrDread <DrDread cheese.com> writes:
On Tuesday, 5 December 2023 at 14:23:26 UTC, Christopher Winter 
wrote:
 On Tuesday, 5 December 2023 at 04:31:28 UTC, Jonathan M Davis 
 wrote:
 This behavior is actually extremely common in OO programming 
 languages.
Yeah, I understand that it is variable shadowing, but this nearly identical situation is rejected by the compiler: ``` void fun(double a) { int a = cast(int) a; <-- Error: variable `a` is shadowing variable `main.fun.a` } ``` I don't really understand why these scenarios would be treated differently by the compiler (I would even argue that my intention here is more explicit vs in the constructor, but not everyone may agree with that).
 The solution to this is to use the this reference when there's 
 any ambiguity.
Yeah, I suppose for me always using `this` is probably the best way to avoid any ambiguity.
you can't really prevent parameter names to be the same as member var names, or else you'll break metaprogramming. so sadly there is no way to solve this ambiguity except writing this. if you want to access a member.
Dec 05 2023
prev sibling parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Tuesday, December 5, 2023 7:23:26 AM MST Christopher Winter via 
Digitalmars-d wrote:
 On Tuesday, 5 December 2023 at 04:31:28 UTC, Jonathan M Davis

 wrote:
 This behavior is actually extremely common in OO programming
 languages.
Yeah, I understand that it is variable shadowing, but this nearly identical situation is rejected by the compiler: ``` void fun(double a) { int a = cast(int) a; <-- Error: variable `a` is shadowing variable `main.fun.a` } ``` I don't really understand why these scenarios would be treated differently by the compiler (I would even argue that my intention here is more explicit vs in the constructor, but not everyone may agree with that).
A big difference is that if you shadow a local variable with another local variable, you don't have any way to differentiate between them. So, when you shadow a local variable, that variable becomes inaccessible within that section of code, whereas with a member variable, you can distinguish with the this reference, and with a variable at module scope, you can differentiate a leading dot. So, the error prevents you from making variables inaccessible, whereas there is no need for such an error when shadowing non-local variables. Also, shadowing non-local variables is sometimes hard to avoid, whereas with a local variable, you're in control of all of the names within the function, and it's pretty rare that there would even arguably be a good reason for shadowing a local variable. If a programmer does it, it's almost certainly a mistake, and they're going to want the compiler to tell them about it. On the other hand, a ton of programmers very much want to shadow member variables in constructors. They want the parameter names to match the names of the variables for documentation purposes, and they're used to dealing with that shadowing, so they don't consider it a big deal. It's rarely a problem in practice, and it's the kind of bug that's found very quickly in testing if you make a mistake. - Jonathan M Davis
Dec 05 2023
parent reply DrDread <DrDread cheese.com> writes:
On Tuesday, 5 December 2023 at 16:30:42 UTC, Jonathan M Davis 
wrote:
 On Tuesday, December 5, 2023 7:23:26 AM MST Christopher Winter 
 via Digitalmars-d wrote:
 [...]
A big difference is that if you shadow a local variable with another local variable, you don't have any way to differentiate between them. So, when you shadow a local variable, that variable becomes inaccessible within that section of code, whereas with a member variable, you can distinguish with the this reference, and with a variable at module scope, you can differentiate a leading dot. So, the error prevents you from making variables inaccessible, whereas there is no need for such an error when shadowing non-local variables. [...]
a better option would be to force the use of this. to access member variables instead of implicitly making them part of the scope. but that ship has sailed a long time ago
Dec 05 2023
parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Tuesday, December 5, 2023 10:45:27 AM MST DrDread via Digitalmars-d wrote:
 On Tuesday, 5 December 2023 at 16:30:42 UTC, Jonathan M Davis

 wrote:
 On Tuesday, December 5, 2023 7:23:26 AM MST Christopher Winter

 via Digitalmars-d wrote:
 [...]
A big difference is that if you shadow a local variable with another local variable, you don't have any way to differentiate between them. So, when you shadow a local variable, that variable becomes inaccessible within that section of code, whereas with a member variable, you can distinguish with the this reference, and with a variable at module scope, you can differentiate a leading dot. So, the error prevents you from making variables inaccessible, whereas there is no need for such an error when shadowing non-local variables. [...]
a better option would be to force the use of this. to access member variables instead of implicitly making them part of the scope. but that ship has sailed a long time ago
There are indeed some languages that do that, but a lot of us would find such a requirement to be extremely annoying, especially since in practice, problems with shadowing member variables are quite rare. - Jonathan M Davis
Dec 05 2023