Ton van den Heuvel Ton van den Heuvel - 2 months ago 14
C++ Question

Why can std::ref be used to use member functions as callable types?

Please consider the following template function, that takes a callable, evaluates it, and returns the result (just for illustrative purposes):

template<typename F, typename... A>
auto evaluate(F&& f, A&&... args) -> decltype(f(std::forward<A>(args)...))
{
return f(args...);
}


This works for free-standing functions, but it breaks when passing member functions as follows for example, where
foo
is an instance of
Foo
:

evaluate(&Foo::bar, foo, ...);


It complains about not being able to call the member function:

error: must use ‘.*’ or ‘->*’ to call pointer-to-member function in ‘f (...)’,
e.g. ‘(... ->* f) (...)’ auto evaluate(F&& f, A&&... args) -> decltype(f(std::forward<A>(args)...))


Wrapping
f
in
std::ref
does allow passing member functions:

template<typename F, typename... A>
auto evaluate(F&& f, A&&... args) -> decltype(std::ref(f)(std::forward<A>(args)...))
...


Why does this work?

Answer

The functionality of making a reference_wrapper callable if the wrapped object is callable is fundamental to the ability to pass references to function objects into standard algorithms and the like.

here we create a tuple of references to function object:

int main()
{
    struct A {
        void operator()() const {
            std::cout << "A\n";
        }
    };

    struct B {
        void operator()() const {
            std::cout << "B\n";
        }
    };

    A a;
    B b;

    auto ab = std::tie(a, b);

    std::get<0>(ab)();
    std::get<1>(ab)();
}

And here we pass a reference to a stateful function object into a standard algorithm:

struct EqualAndCount
{
    EqualAndCount(char sought) : sought_(sought) {}

    template<class R>
    bool operator()(R const& r)
    {
        counter_++;
        return sought_ == r;
    }
    std::size_t counter_ = 0;
    char sought_;
};

int main()
{
    EqualAndCount eq('j');
    auto s = std::string("abcdefghijklmnop");
    auto i = std::find_if(s.begin(), s.end(), std::ref(eq));

    std::cout << "searched " << eq.counter_ << " chars";
    if (i == s.end())
        std::cout << " and did not find it\n";
    else
        std::cout << " and found it\n";
}

expected output:

searched 10 chars and found it

Why does this work?

Because std::reference_wrapper::operator() is written in terms of the mythical INVOKE (up to c++14) and in terms of std::invoke (c++17).

documentation here:

http://en.cppreference.com/w/cpp/utility/functional/invoke