Search code examples
c++11lambdavariadic-templatesisis2

Can a C++11/14 variadic template iterate on arguments to a function?


I'm using variadic templates to capture static type information in Isis2, an atomic multicast library (isis2.codeplex.com). Some Isis2 events are delivered via upcall. For example if you code

Group g("myGroup");
g.Handlers[UDPATE] += [](string& name, Foo& f) { ... your code };
....
g.OrderedSend(UPDATE, "John Doe", new Foo(...));

then on receiving a multicast in group g carrying an update with a string and a Foo object in it, Isis2 would construct a local instance of the Foo object and then upcall to this lambda with the appropriate arguments.

So here's my puzzle. I have the variadic code to scan the arguments to OrderedSend and can capture the static type information needed to build my messages. I end up passing the real OrderedSend method a one-dimensional array of arguments, each with its type, a pointer or safe reference to the data or object, and for an object, the address of a marshaling method. But to use a variadic template to scan the lambda, I need to look at "inner argument list" of the function, in the sense that the object being added to the vector of handlers is a lambda: the type_traits methods will just say that it is an object of type "function". I'm after the string and Foo types, from the argument list of the lambda. But type_traits.h lacks anything for accessing the argument list, as far as I can see.

A GCC-11 specific option is to unmangle the typeid and parse the resulting string. But is there a variadic template feature that would let me get to the argument list of the lambda at compile time?


Solution

  • template<class Sig>
    struct MessageName {
      std::string name;
      MessageName() = delete;
      MessageName( std::string o ):name(o) {}
      MessageName(MessageName&&)=default;
      MessageName(MessageName const&)=default;
      MessageName& operator=(MessageName&&)=default;
      MessageName& operator=(MessageName const&)=default;
    };
    
    // trait to determine if some args are compatible:
    template<class Sig, class...Ts>
    struct is_compatible : std::false_type {};
    template<>
    struct is_compatible<void()> : std::true_type {};
    
    template<class A0, class...Args, class T0, class...Ts>
    struct is_compatible<void(A0, Args...), T0, Ts...>:
      std::integral_constant<bool,
        std::is_convertible<T0, A0>::value
        && is_compatible< void(Args...), Ts... >::value
      >
    {};
    struct HandlerMap {
      template<class Sig>
      void add_handler(
        MessageName<Sig> msg,
        block_deduction< std::function<Sig> > handler
      )
      {
        // ...
      }
      template<class Sig, class...Ts>
      typename std::enable_if<is_compatible<Sig, Ts...>::value>::type
      send_message( MessageName<Sig> msg, Ts&&... ts )
      {
        // ...
      }
    };
    

    The UPDATE token should be of type MessageName. All MessageNames must claim a signature associated with them.

    MessageName< void(std::string const&, Foo const&) > UPDATE{"update"};
    

    like the above.

    Then, when you add a handler, the call to add_handler will check the assigned function against the required signature, and give you a std::function.

    Similarly, when you send a message, the types passed can be checked against the signature. You should even convert the arguments into each of the signature's argument types in the body of the function.

    This moves as much of the type checking as possible to compile time, which is good C++ style.