On 10/7/21 1:35 PM, H. S. Teoh wrote:
> On Wed, Oct 06, 2021 at 11:59:46AM -0400, Steven Schveighoffer via Digitalmars-d wrote:
[...]
> There is one place where I have struggled though, and D might be able to do
better. And that is with optional parentheses and taking the address. When a
property can be either an accessor or a field, then obj.prop
can work the
same. However, &obj.prop
is not the same.
I have solved it by using this stupid function:
auto ref eval(T)(auto ref T t) { return t; }
// instead of &obj.prop
auto ptr = &eval(obj.prop);
IMO, taking the address of something is a low-level operation that
doesn't really belong in higher-level logic. Unsurprisingly, therefore,
it doesn't lend itself well to the malleability that generally applies
in D code.
The fact of taking the address of something imposes the implicit
assumption that that object has an address to be taken in the first
place. Meaning that once you use such an operation, the target object
is no longer Liskov-substitutable with anything else that may not have
an address (e.g. an accessor function). So this imposes limitations on
what kinds of refactoring the code will be amenable to.
To give more context, it is solely for this little function:
auto firstElem(R)(R r) if (hasLvalueElements!R)
{
if(r.empty)
return null;
return &(eval(r.front));
}
Why do I need this function? Because Phobos doesn't give me nice access to things from find
. Instead, it gives me a range. Which is useful for some cases, but for others, I just want to get reference access of the element I was looking for.
So compare the two possibilities:
auto result = someRange.find(elem);
if(!result.empty) {
if(result.front.x > 5)
result.front.x -= 5;
}
// vs.
if(auto v = someRange.find(elem).firstElem) {
if(v.x > 5)
v.x -= 5;
}
For some things, pointers are the best interface.
Sure, I could wrap this up in a type, but it still would need to store a pointer internally (or the range itself), plus a pointer is already doing exactly what I need it to do.
The main issue is that obj.method
means different things depending on context.
As kind of another similar story, A long time ago, an eponymous template allowed some access to things inside the template, and did not always map directly to the eponymous member in some situations. While this allowed greater flexibility, it produced odd behavior. Eventually, all access of the internals of an eponymous template were removed, and it always always just means the eponymous member. This removed some expressiveness, but the result is much much cleaner.
I would like to see something similar for property methods.
> [...]
> While it's nice D has a mechanism to work around this difference, I
find having to use such shims a bit awkward. And it's a direct
consequence of hiding the implementation of a field/property behind
the same syntax. You can find other cases where D can be awkward (such
as typeof(obj.prop)
or checking types regardless of mutability).
What is the "better" answer though? I don't know.
[...]
Not necessarily a better answer: static if to discover whether something
is a field or an accessor, and extract the address appropriately,
possibly encapsulated in a utility function? Not much different from
your .eval hack, though.
This would be horrid. Basically, your code starts looking like:
static if(isAccessor!(obj.foo)) // have fun writing this, there's lots of traps.
return &obj.foo();
else
return &obj.foo;
The eval hack is much much cleaner. It means "this is an expression, not a symbol". Maybe the answer is to do this with some syntax feature, so I can avoid calling silly hack functions.
> The C++ answer is to allow overloading of unary &. However, that opens
up a whole 'nother can o' worms that I would not recommend.
Nope, I would never want that. I want a pointer when I ask for a pointer.
-Steve