In one part of my code, I have an abstract function type Function
which represents any kind of callable and which can be stored in a heterogeneous container, e.g. std::vector<std::unique_ptr<Function>>
:
#include <any>
#include <string>
#include <memory>
#include <vector>
#include <functional>
#include <cassert>
class Function
{
public:
Function(std::string name)
: m_name(name)
{}
virtual ~Function(){}
std::string name() {
return m_name;
}
template <typename... Args>
decltype(auto) operator()(Args&&... args)
{
// delegate to invoke, implementation not relevant for question
}
private:
std::string m_name;
// the following is also simplified for the sake of brevity
virtual std::any invoke(std::initializer_list<std::any> const& args) const = 0;
};
template <typename F>
class FunctionImpl : public Function
{
public:
FunctionImpl(F const& f, std::string name)
: Function(name)
, function(f)
{}
private:
std::any invoke(std::initializer_list<std::any> const& args) const override
{
// implementation not relevant for question
return std::any();
}
F function;
};
using FPointer = std::unique_ptr<Function>;
template <typename F>
FPointer make_function(F const& f, std::string name)
{
return std::make_unique<FunctionImpl<F>>(f, name);
}
Now I want to add a function
using FContainer = std::vector<FPointer>;
template <typename F>
bool contains(FContainer const& vec, F const& f)
{
// ?????
}
which returns true, if the function passed as argument in contained in the container, and false otherwise (and probably in a follow-up step a function that returns a reference to the element in the container, if it is contained). How would I write this kind of function? What are my options?
void bar(){}
void foo(){}
struct AClass {
void MemberFunction1(){}
void MemberFunction2(){}
};
struct ACallableClass
{
void operator()(){}
};
int main()
{
FContainer v;
// function pointer
v.push_back(
make_function(
&foo,
"foo"
)
);
// std::function
v.push_back(
make_function(
std::function<void()>(&foo),
"foo"
)
);
// member function
v.push_back(
make_function(
&AClass::MemberFunction1,
"AClass::MemberFunction1"
)
);
// callable
v.push_back(
make_function(
ACallableClass(),
"CallableClass"
)
);
// lambda
v.push_back(
make_function(
[](){},
"empty lambda"
)
);
assert(contains(v, &foo));
assert(contains(v, std::function<void()>(&foo)));
assert(contains(v, &AClass::MemberFunction1));
assert(!contains(v, [](){})); // every lambda is different
assert(!contains(v, &bar));
assert(!contains(v, std::function<void()>(&bar)));
assert(!contains(v, &AClass::MemberFunction2));
return 0;
}
The best solution I could come up with so far was to write a function template
template <typename F> size_t id(F&& id);
that gives a unique id to any kind of callable. Then Function
could get a new virtual size_t id() const = 0
method, which would be overwritten by Function<F>
. The latter delegates to the free function template. With this, I could compare ids in contains
.
I tried implementing the function template using std::hash
with function pointers, but I got stuck at hashing member function pointers, callable classes and lambdas. Here is my latest approach: https://godbolt.org/z/zx4jnYbeG.
Sorry for the rather lengthy question. Any help would be greatly appreciated!
EDIT 1:
I can live without std::function
support. I would like to support lambdas in principle, but I can live with contains
always returning false for lambdas, which makes sense to me. I do want the code to work with function pointers, callable classes and member functions.
EDIT 2:
Here is a working solution based on the suggestions in xryl669s answer: https://godbolt.org/z/vYGesEsKa. std::function<F>
and F
get the same id, but I suppose this actually make sense, since they are basically equivalent.
typeid
?std::type_info
is comparable and hashable already. – Catholicismoperator==
of stored callable. – Numeraryinvoke
under the hood. Withinvoke
you can threat member functions as normal functions with reference to object as additional first argumentobj.*mptr(args...)
→invoke(mptr, obj, args...)
– Numerary