Jump to page: 1 2
Thread overview
Is it possible to "overload" based on visibility?
Sep 23, 2020
60rntogo
Sep 23, 2020
aliak
Sep 23, 2020
60rntogo
Sep 23, 2020
aliak
Sep 25, 2020
60rntogo
Sep 25, 2020
60rntogo
Sep 25, 2020
60rntogo
Sep 25, 2020
60rntogo
Sep 25, 2020
H. S. Teoh
Sep 26, 2020
60rntogo
September 23, 2020
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.
September 23, 2020
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



September 23, 2020
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
September 23, 2020
On Wednesday, 23 September 2020 at 19:27:13 UTC, Steven Schveighoffer wrote:

> This is a bug in the language.

🤯😆

September 23, 2020
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.
September 25, 2020
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?
September 25, 2020
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
September 25, 2020
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?
September 25, 2020
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
September 25, 2020
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".
« First   ‹ Prev
1 2