c++c++-conceptsc++-templates

How to infer types of arguments from function type to check against list of allowed types using С++ concepts?


I am writing player command handler for some game server. Handler is a some function which accepts arbitrary combination of arguments of allowed types. Allowed types are double, int, std::string, bool. I add new command handler dynamically using method template<typename F> void addCommand(string name, F handler);. But I need to restrict template type F to be:

  1. function/method/lambda
  2. types of arguments of that function should be in the list of allowed types.

I check that F is a function using following concept:

// Helper concept to check if a type is one of the allowed types
template <typename T, typename... AllowedTypes>
concept IsAllowedType = (std::is_same_v<AllowedTypes, T> || ...);

// Concept to check if a function satisfies the requirements
template <typename F, typename... AllowedArgTypes>
concept IsFunctionAndAcceptsAllowedTypes = requires {
    std::invocable<F>;
    // TODO check arg types of F
};

So, the question is, how to infer argument types of provided type F and check them against list of allowed types? I know I can use function_traits helper:

template <typename T>
struct function_traits;

template <typename R, typename... Args>
struct function_traits<std::function<R(Args...)>>
{
    static const size_t nargs = sizeof...(Args);

    typedef R result_type;

    template <size_t i>
    struct arg
    {
        typedef typename std::tuple_element<i, std::tuple<Args...>>::type type;
    };
};

But I can't get my head around this: how to iterate over tuple types to check them in context of templates?


Solution

  • I think this is only possible by defining a helper that has an own requires call:

    #include <functional>
    #include <type_traits>
    
    template <typename T, typename... AllowedTypes>
    concept allowed_type = (std::is_same_v<AllowedTypes, T> || ...);
    
    template <class... Allowed> struct helper {
      template <class R, allowed_type<Allowed...>... Args>
      auto operator()(std::function<R(Args...)> t) -> void;
    };
    
    template <class T, class... Allowed>
    concept function = requires(T t, helper<Allowed...> h) {
      { h(t) } -> std::same_as<void>;
    };
    
    int allowed(int, int, char);
    void disallowed(int, long, char);
    
    int main() {
      function<int, char> auto foo = std::function{allowed};
      // The following fails
      function<int, char> auto bar = std::function{disallowed};
    }