www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Is it possible to "overload" based on visibility?

reply 60rntogo <60rntogo gmail.com> writes:
There are really two questions here, one that I intended to ask 
and one that came out while I was trying to figure out the 
answer. Consider this simple code:

---
module foo;

struct Foo
{
   private int _x;

   int x() const
   {
     return _x;
   }
}
---

If I have an instance of Foo outside of the module I can read the 
value of _x using the property method x, but I can only modify it 
from inside the module using the name _x. This is exactly the 
kind of encapsulation that I often want, but I'm wondering if 
this could be improved a little.

It would be nice if I didn't have to remember if I need to use _x 
or x depending on the context. Instead I would like to use only 
one name, say x, from both inside and outside of the module and 
have compiler complain if I'm trying to modify it from the 
outside.

My naive attempt was this:

---
import std : writeln;

private int _x;

private ref int x() return
{
   writeln("ref int");
   return _x;
}

int x() const
{
   writeln("int");
   return _x;
}
---

At first I didn't even expect this to compile, but it does and I 
can call these methods. I believe an overload is chosen based on 
the type qualifier of a Foo instance. What is truly unexpected is 
that if I call x on a mutable object even outside of the module, 
then the first overload is called!

So my questions are:

1. Can I achieve my original goal of being able to refer to _x by 
one name, so that I have read only access from outside the module 
and read/write access from inside?
2. Is the behavior that allows me to call the private method 
intended? This is such a blatant violation of encapsulation that 
it feels like a bug either in the language or the implementation.
Sep 23 2020
next sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 9/23/20 2:38 PM, 60rntogo wrote:
 So my questions are:
 
 1. Can I achieve my original goal of being able to refer to _x by one 
 name, so that I have read only access from outside the module and 
 read/write access from inside?
I would guess no. You have to use different names.
 2. Is the behavior that allows me to call the private method intended? 
 This is such a blatant violation of encapsulation that it feels like a 
 bug either in the language or the implementation.
This is a bug in the language. Either varying ONLY by visibility of an overload should be disallowed, or you shouldn't have access to the private x. I don't know which one the answer is, but certainly the current behavior is erroneous. -Steve
Sep 23 2020
next sibling parent aliak <something something.com> writes:
On Wednesday, 23 September 2020 at 19:27:13 UTC, Steven 
Schveighoffer wrote:

 This is a bug in the language.
🤯😆
Sep 23 2020
prev sibling parent reply 60rntogo <60rntogo gmail.com> writes:
On Wednesday, 23 September 2020 at 19:27:13 UTC, Steven 
Schveighoffer wrote:
 This is a bug in the language.
Is this a known bug? If not, it should be reported. I came up with an answer to my original question that sort of works: --- module foo; struct Foo { private int x; } int x(Foo f) { return f.x; } --- The downside is that if I don't want to import all of foo at once, then I have to import both Foo and x, but then I can read x from outside the module and modify it form inside as I wanted. Are there any drawbacks of this approach that I'm not seeing?
Sep 25 2020
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 9/25/20 3:43 AM, 60rntogo wrote:
 On Wednesday, 23 September 2020 at 19:27:13 UTC, Steven Schveighoffer 
 wrote:
 This is a bug in the language.
Is this a known bug? If not, it should be reported.
I don't know, you can search for and report it here: https://issues.dlang.org
 
 I came up with an answer to my original question that sort of works:
 
 ---
 module foo;
 
 struct Foo
 {
    private int x;
 }
 
 int x(Foo f)
 {
    return f.x;
 }
 ---
 
 The downside is that if I don't want to import all of foo at once, then 
 I have to import both Foo and x, but then I can read x from outside the 
 module and modify it form inside as I wanted. Are there any drawbacks of 
 this approach that I'm not seeing?
Wow, this is actually quite clever! I think it's a very valid solution. The only thing I would caution is that it takes Foo by value, which means it's going to make a copy of everything. Your toy example, that's OK, but if Foo is complex or has a significant copy constructor, it might be slow. You can use auto ref to alleviate that: int x()(auto ref Foo f) // needs to be a template for auto ref to work -Steve
Sep 25 2020
parent reply 60rntogo <60rntogo gmail.com> writes:
On Friday, 25 September 2020 at 13:15:27 UTC, Steven 
Schveighoffer wrote:
 I don't know, you can search for and report it here: 
 https://issues.dlang.org
I find it quite hard to search for anything here, but I couldn't find anything similar so I submitted a bug report.
 You can use auto ref to alleviate that:

 int x()(auto ref Foo f) // needs to be a template for auto ref 
 to work
That's a good point, thanks. Since we are on that topic, how would that differ from the following? int x(in Foo f) And going further, if instead of int I wanted to return something that might also be expensive to copy, what would be the best way to declare the function?
Sep 25 2020
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 9/25/20 10:12 AM, 60rntogo wrote:
 On Friday, 25 September 2020 at 13:15:27 UTC, Steven Schveighoffer wrote:
ou can use auto ref to alleviate that:
 int x()(auto ref Foo f) // needs to be a template for auto ref to work
That's a good point, thanks. Since we are on that topic, how would that differ from the following? int x(in Foo f)
in does not mean "take by reference", it means "scope const"
 And going further, if instead of int I wanted to return something that 
 might also be expensive to copy, what would be the best way to declare 
 the function?
It depends on if you want to return a copy. If you want to return a reference if the source is a reference, use auto ref on the return as well. But if you still want to protect the internal data, it would have to be const. -Steve
Sep 25 2020
parent reply 60rntogo <60rntogo gmail.com> writes:
On Friday, 25 September 2020 at 14:21:59 UTC, Steven 
Schveighoffer wrote:
 in does not mean "take by reference", it means "scope const"
I'm not sure that I really understand scope, but I read https://dlang.org/spec/function.html#param-storage as saying "in means take by value or reference depending on what is better optimized". Is that not what we want here?
 It depends on if you want to return a copy. If you want to 
 return a reference if the source is a reference, use auto ref 
 on the return as well. But if you still want to protect the 
 internal data, it would have to be const.
Right, again I'm wondering if there is a way of saying "just figure out if it's more optimal to return by value or const reference".
Sep 25 2020
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 9/25/20 11:13 AM, 60rntogo wrote:
 On Friday, 25 September 2020 at 14:21:59 UTC, Steven Schveighoffer wrote:
 in does not mean "take by reference", it means "scope const"
I'm not sure that I really understand scope, but I read https://dlang.org/spec/function.html#param-storage as saying "in means take by value or reference depending on what is better optimized". Is that not what we want here?
That's new, unreleased (available in 2.094.0), and requires the -preview=in switch. I wouldn't depend on that mechanism yes.
 Right, again I'm wondering if there is a way of saying "just figure out 
 if it's more optimal to return by value or const reference".
If the input is not ref, you should not return by ref, because then you would be returning a reference to local stack data that is about to be destroyed. The only way to say "make this const ONLY if it's ref" is to overload the function with the proper attributes for the proper situations. Otherwise, you can just return const auto ref. -Steve
Sep 25 2020
next sibling parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 9/25/20 11:21 AM, Steven Schveighoffer wrote:
 I wouldn't depend on that mechanism yes.
*yet*. -Steve
Sep 25 2020
prev sibling parent reply 60rntogo <60rntogo gmail.com> writes:
On Friday, 25 September 2020 at 15:21:22 UTC, Steven 
Schveighoffer wrote:
 If the input is not ref, you should not return by ref, because 
 then you would be returning a reference to local stack data 
 that is about to be destroyed.
Yes, I understand that. What I'm really after at this point is that I would like to write a clever mixin that would handle all of these decisions for me. It should generate a function that takes arguments and returns the result by value or const reference depending on what is more appropriate for the given types. I was under the impression that this could be accomplished using in or some other qualifiers.
Sep 25 2020
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Fri, Sep 25, 2020 at 05:58:08PM +0000, 60rntogo via Digitalmars-d-learn
wrote:
 On Friday, 25 September 2020 at 15:21:22 UTC, Steven Schveighoffer wrote:
 If the input is not ref, you should not return by ref, because then
 you would be returning a reference to local stack data that is about
 to be destroyed.
Yes, I understand that. What I'm really after at this point is that I would like to write a clever mixin that would handle all of these decisions for me. It should generate a function that takes arguments and returns the result by value or const reference depending on what is more appropriate for the given types. I was under the impression that this could be accomplished using in or some other qualifiers.
You probably need to use the long-form of templates, with separate function declarations, to accomplish this. E.g.: template myFunc(Args...) { static if (shouldReturnByRef!Args) ref ReturnType myFunc(Args args) { ... // implementation here } else // non-ref return ReturnType myFunc(Args args) { ... // implementation here } } The reason is that `ref` return is an attribute of the *function*, not the return type, so you can't just use an `auto` return type and have the compiler infer it. T -- What doesn't kill me makes me stranger.
Sep 25 2020
parent 60rntogo <60rntogo gmail.com> writes:
On Friday, 25 September 2020 at 18:58:54 UTC, H. S. Teoh wrote:
 You probably need to use the long-form of templates, with 
 separate function declarations, to accomplish this. E.g.:

 ...
Alright, but your example still contains shouldReturnByRef which presumably I need to implement myself. But that's an optimization detail that I'd rather leave up to the compiler, is that possible?
Sep 26 2020
prev sibling parent reply aliak <something something.com> writes:
On Wednesday, 23 September 2020 at 18:38:53 UTC, 60rntogo wrote:
 There are really two questions here, one that I intended to ask 
 and one that came out while I was trying to figure out the 
 answer. Consider this simple code:

 [...]
Yeah, you can make a property setter: private void x(int newValue) { _x = newValue }
 2. Is the behavior that allows me to call the private method 
 intended? This is such a blatant violation of encapsulation 
 that it feels like a bug either in the language or the 
 implementation.
Definitely sounds like a bug! Feels like this has got to be a regression because I just tried this: struct Foo { private void f() {} void f(int i) {} } And Foo.f() is callable from outside the module: https://run.dlang.io/is/FVyw7u
Sep 23 2020
parent 60rntogo <60rntogo gmail.com> writes:
On Wednesday, 23 September 2020 at 19:26:43 UTC, aliak wrote:
 Yeah, you can make a property setter:

 private void x(int newValue) { _x = newValue }
I'm aware of this, but it does not achieve what I asked for. It only allows me to assign to _x, it doesn't give me a reference to x, so I cannot use it with say += or any other function that takes int by mutable reference.
Sep 23 2020