0

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>
{};
chili
  • 665
  • 8
  • 20
  • 3
    Please include your example code in the question itself. –  May 21 '19 at 14:58
  • @Frank Yes that solved my main issue, thank you. I'll troll around a bit more for the std::is_invokable aspect and maybe post it as its own question if I cannot find anything. – chili May 21 '19 at 15:34
  • 1
    Suggestion: try with `using call_type = function_traits;`, instead of `using call_type = function_traits;` (I mean: `&F::operator()`, not `&F::type::operator()`), inside the "functor" version of your `function_trait`. – max66 May 21 '19 at 16:50
  • @max66 that works, and you are a golden god. – chili May 22 '19 at 02:32

0 Answers0