variadic-templatesvariadic-functionsc++17template-meta-programmingtype-traits

Is a is_variadic type trait possible in C++17?


Is it possible, in C++17, to design a type trait which would detect whether a callable is variadic (and therefore can take an arbitrarily long number of parameters) or not?

template <class Callable>
struct is_variadic;

I currently do not see how to do that, but I cannot convince myself that it's not doable. So if it's doable what would that look like?


Solution

  • The is_variadic type trait is true if a callable type is variadic.

    It means that the type trait must be partial-specialized to support a function type, pointer to function type and member function type that can accept a variadic number of parameters. The latter is necessary to support both lambdas and structures that have at least one overloaded operator() (callable struct).

    However, there is a limitation: if the type trait can be partial-specialized to detect whether a type is a function type, it is not possible to use the same method for the callable structs. A possible solution requires to exploit SFINAE to detect whether the type is a class with an overloaded operator() as member function. Specifically, it is sufficient to verify that the expression &T::operator() be well-formed. If the condition is satisfied, the deduced type can be extracted and then the partial-specialization for member function types can be used.

    These are implemented in the detail namespace to be used as helpers. Even if the following implementation does not provide them for simplicity, other partial-specializations may be added to support variations of the basic function type and member function type (ex. &, const&, volatile&).

    However, the type trait has two limitations.

    1. It does not support non-specialized template callable types. The problem arises because the type trait does not require to specify parameter types, and therefore it is not possible to deduce the specialization of a template.
    2. The type trait can not support callable structs that have more overloaded operator()s. Indeed, it is not possible to use them in an expression if parameter types are not known.
    namespace detail {
      template <typename>
      struct is_variadic_helper
       : std::false_type {};
    
      template <typename R, typename ...Args>
      struct is_variadic_helper<R(Args......)>
       : std::true_type {};
    
      template <typename R, typename ...Args>
      struct is_variadic_helper<R(*)(Args......)>
       : std::true_type {};
     
      template <typename T, typename R, typename ...Args>
      struct is_variadic_helper<R(T::*)(Args......)>
       : std::true_type {};
    
      template <typename T, typename = void>
      struct is_variadic
       : is_variadic_helper<T> {};
    
      template <typename T>
      struct is_variadic<T, std::void_t<decltype(&T::operator())>>
       : is_variadic_helper<decltype(&T::operator())> {};
    }
    
    template <typename T>
    struct is_variadic
     : detail::is_variadic<std::remove_cv_t<T>> {};
    
    template <typename T>
    inline constexpr bool is_variadic_v = is_variadic<T>::value;