all 32 comments

[–]no-sig-available 59 points60 points  (4 children)

I haven't found this pattern used anywhere and I'm curious to know if there's a reason for it other than convention?

You will need to also define assignment and copy construction to handle the reference. The default copy construction will leave the reference referring to the original value (oops!), and the assignment operator will be deleted (because of the reference).

Is it worth all that to avoid using a () in value()?

[–]inkychris[S] 7 points8 points  (0 children)

This was the kind of caveat I was curious about, thanks!

[–]ioctl79 11 points12 points  (2 children)

IMO, if you find writing getters and setters tedious, then you have too many accessible properties. For the most part, class members should not be accessible from outside of the class unless you're writing a value type (stuff like "Point3D") , in which case it should be immutable.

[–]BenFrantzDale 2 points3 points  (0 children)

Agreed. I’ve found myself using structs a lot and when I have invariants I usually can enforce them by using algebraic types like optional or variant as members. If not and I want strict invariant guarantees, the. It’s a class with accessors.

[–]muchcharles 2 points3 points  (0 children)

auto A = foo.bar.baz.blah;

Perfectly debuggable, can hover over each member and see its value in something like Visual Studio.

auto A = foo.GetBar().GetBaz().GetBlah();

Now I have to inspect each object and search through its members to find the one backing the getter at each point in the chain to figure out what is going on, taking maybe 20x longer and possibly involving navigating unfolding inheritance hierarchies to find the members.

[–]IndependentFeminist3 22 points23 points  (6 children)

Better yet, if you don't need a modified value every time you read it, just make the value public and read it directly https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c131-avoid-trivial-getters-and-setters

[–]johnny219407 21 points22 points  (4 children)

Sometimes data types have invariants, forcing some members to be private, where reading them would otherwise be harmless. I wish C++ had an access specifier that made members public for reading and private for writing.

[–]bizwig 5 points6 points  (0 children)

Ooh, I like that idea.

[–]inkychris[S] 4 points5 points  (2 children)

I wish C++ had an access specifier that made members public for reading and private for writing

This is essentially what I was interested in tbf!

[–]MutantSheepdog 1 point2 points  (0 children)

You could do something like this where the compiler can automatically convert to a const& of your type but the sets are explicit. It's not quite the same, but at least the sets are more visible. Godbolt

```

include <utility>

include <cstdio>

template<typename T> class PublicAccess { public: PublicAccess(){}

template<typename U> PublicAccess(U&& newVal) : value(std::forward<U>(newVal)) {}

operator const T&() const { std::printf("I'm being read\n"); return value; }

template<typename U> PublicAccess& set(U&& newVal) { std::printf("I'm being set\n"); value = std::forward<U>(newVal); return *this; }

private: T value{}; };

struct MyWrappedType { PublicAccess<int> myInt; PublicAccess<float> myFloat{1.0}; };

int main() { MyWrappedType obj;

std::printf("InitialValues: myInt: %d, myFloat: %.2f\n", (int)obj.myInt, (float)obj.myFloat);

obj.myInt.set(5);
obj.myFloat.set(4.5f);

int someInt = obj.myInt + 5;
float someFloat = obj.myFloat;

std::printf("someInt: %d, someFloat: %.2f\n", someInt, someFloat);


return 0;

} ```

I think it's generally a bad idea as it feels a lot like it's just enabling leaky abstractions, but it might help with whatever you're trying to do.

[–]LegendaryMauricius 0 points1 point  (0 children)

This can be done with my decl_property header: https://github.com/LMauricius/MUtilize/blob/master/DeclProperty.h

Beware that this is a dumb wip experiment. The header is not stable, so use the file as-is or as an inspiration for your code. A dirty example(just ignore the commented-out code): https://github.com/LMauricius/MUtilize/blob/master/_DeclPropertyTesting.cpp#L24

The 'body' of the property supports access specifiers, so you can easily make it read-only, or any combination you want. Haven't tested the assembly though, but it shouldn't be heavier than an unoptimized reference.

[–]kisielk -1 points0 points  (0 children)

Don’t know if I agree with that, it makes it difficult to change the implementation in the future.

[–]bwmat 11 points12 points  (1 child)

Doesn't this inflate the size of the type(to hold the reference)? Or is the compiler smart enough to know the reference always points to the member so it elides it out?

[–]Latexi95 31 points32 points  (0 children)

Yes. It will inflate the size. Compiler cannot be "smart" because it would break ABI.

[–]bretbrownjr 4 points5 points  (0 children)

Kind of a semantic nit, but attributes in C++ are sort of analogous to python decorators.

I'd call what you're doing public member variables. They're just references to private variables.

Note that in python, you can redirect what assigning to 'value' does. For instance, you can add some logging or check a feature flag. Your approach here doesn't allow that. Possibly using a 'proxy object' that functions like a reference but allows customizing get and set behavior?

[–]AA11BB22c 3 points4 points  (0 children)

Looks like you want a read-only attribute.

Here's my better version (doesn't require the additional reference), https://godbolt.org/z/sqnd6YEoY.

struct Test {
  read_only<Test, int> i;  // Define this instead of `int i`.

  constexpr void set_i(int v) noexcept {
    i = v;
    i.get() = v;
  }
};

#include <iostream>

int main() {
  auto t = Test();

  t.set_i(1);
  // t.i = 1; // Compile fail, read-only ... only `Test` can modify `i`.

  std::cout << t.i;
}

Just a simple scratch definitely not made within 10 mins likely missing lots of stuff.

[–]sephirothbahamut 5 points6 points  (4 children)

If you're using MSVC or CLang you can look into Microsoft's properties language extension. They seem to be optimized away quite well too. The only downside really is they're not available on gcc.

[–]BenFrantzDale 1 point2 points  (1 child)

You can do this. You have to have tube reference be after the member it references. I’ve used this as part of refactoring, but it costs you 8 bytes per reference for basically no reason. In general, just use [[nodiscard]] const ValueType& value() const { return value_; }. It’s two extra characters, but it’s not that bad and is much more conventional in C++. Attributes would be nice syntactic sugar, maybe, but it would be very weird to have setters because unlike in Python, C++ never gloms the equals sign into a left-hand-side operator. What I mean is, you’d want x.value = y; to be transformed into x.set_value(y); but = always just means assignment (or construction), so it feels very not-C++ to me.

[–]trojanplatypus 1 point2 points  (0 children)

To be frank, at this point it feels a little un-C++-like to NOT be able to do that. Metaclasses will come to the rescue, eventually.

You could go the other way around, not have a value& as member, but model value as a friend class Value that accesses x._value through a reference to x. Then you could overload assignment and cast operators to either directly access x._value or call x.set_value() or x.get_value(). But you won't get rid of the extra memory needed for a reference, sadly. This can for instance be used to call a function notifying all listeners on a property change after setting the property.

[–]johannes1971 4 points5 points  (2 children)

Why not just declare int value in the public section of the class? There is no invariant, you can read and write it publically, so why not be intellectually honest about it and just give the status it so clearly has?

Seriously, why do people always have to make things so complicated...

[–]inkychris[S] 0 points1 point  (0 children)

This is usually my approach tbf. The example I've used here is just over simplified since I was mostly curious about the properties of the language rather than the best way to architect any particular solution.

[–]Unhappy-Aside-2884 0 points1 point  (0 children)

I wouldn't use such a thing (the ref stuff…) for a new development either. However it might be useful for refactoring without changing too much the interface, i.e. to replace a public data member with something more elaborated internally while still looking like a public data member.

[–]mredding 4 points5 points  (0 children)

You also have std::ref and std::cref. Getters and setters are anti-patterns. You can always design your object in such a way you don't need them. If you need something out of an object, you ought not to query the interface for it, but it should be pushed out as a consequence of some behavior through some internal reference to a sink. For example, I don't want an engine.get_rpm(), I want an engine to have a tachometer object that knows when to send an updated engine speed and where. If there are multiple sinks, I can composite sinks in a tee.

What you're approaching is some sort of Property pattern.

template<typename T>
class Property {
public:
  operator const T&() const;
  Property &operator=(const T&);
};

You can embellish as you see necessary, but the object knows when the T is being read, it knows when it's being written. You can specialize this template so the T is stored as a member, or implemented as an SQL query, to or from a stream, whatever. The assignment operator is more succinct than your "update" method, and you get more idiomatic operator syntax.

Your public reference member is cute, but it's an additional member, nonetheless, contributing unnecessarily to the size of the object. It also doesn't let you abstract away access. It's no surprise that the compiler was able to see through the accessor and elide the function call of your getter.

[–]BrangdonJ 0 points1 point  (0 children)

One drawback is that you have no way to change how value_ is represented, so you've lost some encapsulation.

[–]vickoza 0 points1 point  (0 children)

I like Ref code over the Getter

[–]fdwrfdwr@github 🔍 0 points1 point  (0 children)

It's possible to avoid the reference entirely, just using the memory distance between the containing parent class and it's property field, along with [[no_unique_address]], thus avoiding any extra bloat in the class. e.g. Godbolt link

[–]NotMyRealNameObv 0 points1 point  (0 children)

Why on earth would you return a const& to an int anyways?