0

(This question popped to my mind after reading this one and its accepted answer.)

Assume a class Foo that you cannot modify, that has a public member bar, but no getter for it.

You might want to write a function to get that memeber when passed a Foo, so that you can use it in higher order functions, such as std::transform and similar.

In other words, given a class like this

struct Foo {
    int bar{};
    ~Foo() { bar = -1; } // on most compilers this helps "verifying" that the object is dead
};

I would like some getFoo such that getBar(expr-of-type-Foo) has the same semantics as expr-of-type-Foo.bar, where expr-of-type-Foo is any object of type Foo, which can be a prvalue, an xvalue, or an lvalue.

How do you write it?

What I initially came up with is:

        constexpr auto getBar = overload(
            [](auto& foo) -> decltype(auto) { return (foo.bar); },
            [](auto const& foo) -> decltype(auto) { return (foo.bar); },
            [](auto&& foo) -> auto { return foo.bar; }
        );

(-> auto is redundant, but I think it's informative in this context.)

My reasoning was as follows.

I need this getBar to behave differently for the different value categories, so it's a good idea to write it as an overload set; for this purpose I'm using boost::hana::overload.

However I'm not sure the solution I found it's minimal (assuming it is sufficient).

For instance,

  • Given one of the overloads given I've overloaded on auto&/auto const&/auto&&, the latter will only catch rvalues, so in the other two cases I know I want to return a reference, so I could return -> auto& and -> auto const& instead of decltype(auto) and even remove the parenthesis from the returned expression:

        constexpr auto getBar = overload(
            [](auto& foo) -> auto& { return foo.bar; },
            [](auto const& foo) -> auto const& { return foo.bar; },
            [](auto&& foo) -> auto { return foo.bar; }
        );
    
  • At this, point, though, I don't think I need two overloads (auto&) -> auto&/(auto const&) -> auto const&, because the former will naturally resolve to the latter when fed with a const expression, so I think I could go for this:

        constexpr auto getBar = overload(
            [](auto& foo) -> auto& { return foo.bar; },
            [](auto&& foo) -> auto { return foo.bar; }
        );
    

But I don't see a way to simplify it further, at the moment.

Here are my attempts to test it:

#include<assert.h>
#include<boost/hana/functional/overload.hpp>

struct Foo {
    int bar{};
    ~Foo() {
        bar = -1;
    }
};

int main() {
    {
        constexpr auto getBar = overload(
            [](auto&& foo) -> auto { return foo.bar; },
            [](auto& foo) -> auto& { return foo.bar; }
        );
        {
            Foo foo{3};
            assert(&getBar(foo) == &foo.bar);
            assert(getBar(foo) == 3);

            foo.bar = 4;
            assert(foo.bar == 4);
            assert(getBar(foo) == 4);

            getBar(foo) = 5;
            assert(foo.bar == 5);
            assert(getBar(foo) == 5);
        }
        {
            Foo const foo{3};
            assert(&getBar(foo) == &foo.bar);
            assert(getBar(foo) == 3);

            //foo.bar = 3;     // Expectedly fails to compile.
            //getBar(foo) = 3; // Expectedly fails to compile.
        }
        {
            auto const& foobar = getBar(Foo{3});
            assert(foobar == 3);
            //foobar = 5; // Expectedly fails to compile.
        }
        {
            //auto& foobar = getBar(Foo{3}); // Expectedly fails to compile.
            //auto& foobar = Foo{3}.bar;     // Expectedly fails to compile.
        }
    }
}
Enlico
  • 23,259
  • 6
  • 48
  • 102
  • 1
    `#define getBar(foo) (foo).bar`? – NathanOliver Nov 10 '22 at 20:35
  • Ahah, I expected a comment from [you](https://stackoverflow.com/a/65811783/5825294). Anyway, yes, the macro is easy, but doesn't seem to be necessary, in this case, no? I know it is necessary [in other cases](https://stackoverflow.com/questions/74335759/how-do-i-use-member-functions-to-a-standard-library-ranges-operation). I was thinking of using a macro to _make_ a `getWhatever` function. – Enlico Nov 10 '22 at 20:38
  • Ah, missed you were passing this on as a function somehow. Yeah, the macro wont help with that. I think what you have might be the shortest bit of code to get what you want. – NathanOliver Nov 10 '22 at 20:47
  • Slightly simpler without using `overload` would just be standalone functions: `auto& getBar(Foo &f) { return f.bar; }` and/or `const auto& getBar(const Foo &f) { return f.bar; }`, depending on your needs. Or maybe even a single function `template auto& getBar(T &&t) { return t.bar; }` that uses a [*forwarding reference*](https://en.cppreference.com/w/cpp/language/reference#Forwarding_references) to handle different types of reference inputs. – Remy Lebeau Nov 10 '22 at 21:37

1 Answers1

5

Well, for minimal work:

const auto getBar = std::mem_fn(&Foo::bar);

This particular use case appears to do what you want. In C++20, it was upgraded to be constexpr, but that might or might not be available. Unlike the other question, which has overloadable member functions, a member variable isn't ambiguous. So I would fall back to the above.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Dave S
  • 20,507
  • 3
  • 48
  • 68