Search code examples
c++genericsgetterperfect-forwardingnon-member-functions

What is the minimal way to write a free function to get the member of a class?


(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.
        }
    }
}

Solution

  • 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.