So I tried to apply the code shown in this blog post, and while it seems to be working for free functions, it falls flat on its face when I try to use it with a lambda function.
Here is a fairly minimal demo of the problem.
Note the constexpr bool
"switch" using_lambda
I've set up. When set to false
, the program compiles and runs fine. When set to true
, the compiler (gcc/c++17) dies with these diagnostics:
In file included from prog.cc:7: function_traits.h: In instantiation of 'struct function_traits >': prog.cc:30:37: required from 'void EventDispatcher::Register(F&&) [with F = main()::]' prog.cc:71:10:
required from here function_traits.h:47:45: error: no type named 'type' in 'struct main()::' using call_type = function_traits; ^~ function_traits.h:47:45: error: no type named 'type' in 'struct main()::' function_traits.h: In instantiation of 'struct function_traits >::argument<0>': prog.cc:31:66: required from 'void EventDispatcher::Register(F&&) [with F = main()::]' prog.cc:71:10: required from here function_traits.h:47:45: error: no type named 'type' in 'struct main()::'
I'm afraid I'm a little (a lot) out of my depth here. Is it possible to get this working, and if so how? Or is there a completely different approach that will serve the same goals better?
Also, as an afterthought, I am having trouble getting std::is_invocable
working as well (see line 28). I suspect it only returns true when the invocable object is invocable for the specified argument types. I only wish to detect whether something is invocable with any argument(s). If someone could shed some light on how this may be done that would also be appreciated.
main.cpp:
// main.cpp
#include <iostream>
#include <unordered_map>
#include <functional>
#include <typeinfo>
#include <typeindex>
#include <type_traits>
#include "function_traits.h"
class Event
{
public:
virtual ~Event()
{}
protected:
Event() = default;
};
class TestEvent : public Event
{};
class EventDispatcher
{
public:
template<class F>
void Register( F&& f )
{
// this static assert seems to always fail
//static_assert(std::is_invocable<F>::value,"Handler must be invocable.");
using Traits = function_traits<F>;
static_assert(Traits::arity == 1,"Handler must have exactly 1 parameter.");
using PCRef = typename Traits::template argument<0>::type;
using PC = typename std::remove_reference<PCRef>::type;
using P = typename std::remove_const<PC>::type;
static_assert(std::is_base_of<Event,P>::value && !std::is_same<Event,P>::value,"Handler parameter must be derived from Event.");
static_assert(std::is_reference<PCRef>::value,"Handler parameter must be passed by reference.");
static_assert(std::is_const<PC>::value,"Handler parameter must be constant reference.");
// wrap f in a lambda that casts to specific event handled
handlers[typeid(P)] = [f]( const Event& e )
{
f( static_cast<const P&>(e) );
};
}
void Dispatch( const Event& e ) const
{
auto i = handlers.find( typeid(e) );
if( i != handlers.end() )
{
i->second( e );
}
}
private:
std::unordered_map<std::type_index,std::function<void(const Event&)>> handlers;
};
void FreeFunctionHandler( [[maybe_unused]] const TestEvent& e )
{
std::cout << "TestEvent fired; handled by free function." << std::endl;
}
int main()
{
constexpr bool using_lambda = true;
EventDispatcher ed;
if constexpr( using_lambda )
{
ed.Register([]( [[maybe_unused]] const TestEvent& e )
{
std::cout << "TestEvent fired; handled by lambda function." << std::endl;
});
}
else
{
ed.Register( FreeFunctionHandler );
}
ed.Dispatch( TestEvent{} );
return 0;
}
function_traits.h:
// function_traits.h
#pragma once
#include <tuple>
template<class F>
struct function_traits;
// function pointer
template<class R,class... Args>
struct function_traits<R( *)(Args...)> : public function_traits<R( Args... )>
{};
template<class R,class... Args>
struct function_traits<R( Args... )>
{
using return_type = R;
static constexpr std::size_t arity = sizeof...(Args);
template <std::size_t N>
struct argument
{
static_assert(N < arity,"error: invalid parameter index.");
using type = typename std::tuple_element<N,std::tuple<Args...>>::type;
};
};
// member function pointer
template<class C, class R, class... Args>
struct function_traits<R(C::*)(Args...)> : public function_traits<R(C&,Args...)>
{};
// const member function pointer
template<class C, class R, class... Args>
struct function_traits<R(C::*)(Args...) const> : public function_traits<R(C&,Args...)>
{};
// member object pointer
template<class C, class R>
struct function_traits<R(C::*)> : public function_traits<R(C&)>
{};
// functor
template<class F>
struct function_traits
{
private:
using call_type = function_traits<decltype(&F::type::operator())>;
public:
using return_type = typename call_type::return_type;
static constexpr std::size_t arity = call_type::arity - 1;
template <std::size_t N>
struct argument
{
static_assert(N < arity,"error: invalid parameter index.");
using type = typename call_type::template argument<N + 1>::type;
};
};
template<class F>
struct function_traits<F&> : public function_traits<F>
{};
template<class F>
struct function_traits<F&&> : public function_traits<F>
{};