After looking at a post on CR I found that pointers to member functions are incredibly hard to use, especially as callbacks. The current solution doesn't solve the problem mentioned in the linked post, but tries to get rid of the type erasure (i.e. std::function<>
) by making the target type explicit. I tried to solve lifetime/ownership issue with something else than std::shared_ptr<>
, but found that it is impossible without relying on programmer to take care of that. So, I ended up with std::shared_ptr<>
as a solution.
So, here is the code:
#pragma once
#include <memory>
#include <utility>
template <typename T, typename Func>
class member_callback;
template <typename T, typename R, typename ... ArgTypes>
class member_callback<T, R(ArgTypes...)>
{
std::shared_ptr<T> object;
R(T::* callback)(ArgTypes ...);
public:
using function_pointer = R(T::*)(ArgTypes ...);
member_callback(const std::shared_ptr<T>& ptr, R(T::* function)(ArgTypes...)) :
object(ptr),
callback(function)
{}
member_callback(std::shared_ptr<T>&& ptr, R(T::* function)(ArgTypes...)) :
object(std::move(ptr)),
callback(function)
{}
R operator()(ArgTypes&& ... args)
{
return ((*object).*callback)(std::forward<ArgTypes>(args)...);
}
std::shared_ptr<T> get()
{
return object;
}
function_pointer get_function()
{
return callback;
}
};
It does one tradeoff though, you can't make a vector of them and call member functions of completely unrelated classes. Nevertheless, you'll still be able to invoke virtual functions, and they will work as expected according to this answer.
From the first glance it might look useless. But it is not! It becomes extremely useful when you're making a menu (1. ... 2. ... 3. ...) and want to invoke a function on the single object. So, here is the example:
#include "member_callback.hpp"
#include <iostream>
#include <vector>
class integer
{
int x;
public:
integer():
x(0)
{}
integer& operator++()
{
++x;
return *this;
}
integer& operator--()
{
--x;
return *this;
}
friend std::ostream& operator<<(std::ostream& os, const integer& i);
};
std::ostream& operator<<(std::ostream& os, const integer& i)
{
os << "Currently number is: " << i.x << '\n';
}
void print_menu()
{
static auto message = "Type the specified number to invoke the command.\n";
static auto menu = "0. Increment\n1. Decrement\nAnything else to exit\n";
std::cout << message << menu;
}
int main()
{
auto object = std::make_shared<integer>();
std::vector<member_callback<integer, integer&()>> actions{{object, &integer::operator++},
{object, &integer::operator--}};
std::cout << "Welcome to dummy program!\n";
std::cout << *object;
unsigned int response = -1;
print_menu();
std::cin >> response;
while (response < 2)
{
actions[response]();
std::cout << *object;
print_menu();
std::cin >> response;
}
std::cout << "Goodbye!\n";
}
If code will ever get changed you can find it here.
I would like a review to focus on the easy of use. Also, does the code provide fine grained control? It will require dealing with std::shared_ptr<>
, but I think it is mostly ok. Any other review points are welcome!
shared_ptr
you will need to dereference it. \$\endgroup\$ – Martin York Dec 5 '16 at 18:08