Laith
Laith

Reputation: 1326

How to check the type of passed arguments to variadic function

I'm new to variadic templates and for the sake of learning consider the following function

template <typename T, typename... args>
T* make_arr(args... arg) {

   // Code to check if passed args are of the same type

   T* arr = new T[sizeof...(arg)]{ arg... };
   return arr;
}

I have two questions:

  1. I want the function to be templated and I want the passed arguments to be of the same type so the question: is it possible to check if passed arguments are of the same type ?
  2. Is it possible to deduce the type of the array pointer by deducing the type of args..., I mean without using <typename T>? ... I used decltype(arg) but it didnt work ...

Note: please edit the title question if it is not appropriate ... thanks

Upvotes: 4

Views: 2319

Answers (3)

Aaron McDaid
Aaron McDaid

Reputation: 27183

Start with a constexpr bool function to check if all the booleans are true. This will be useful when checking that all the is_same calls are true.

constexpr bool all() {
    return true;
}
template<typename ...B>
constexpr bool all(bool b, B... bs) {
    return b && all(bs...);
}

Anyway, here is the make_arr function:

template <typename... args
, class ...
, typename T = std::tuple_element_t<0, std::tuple<args...>>
, typename = std::enable_if_t< all(std::is_same<T, args>{}...) >
>
T* make_arr(args&&... arg) {
    static_assert( all(std::is_same<T, args>{}...) ,"");
    T* arr = new T[sizeof...(arg)]{ std::forward<args>(arg)... };
    return arr;
}

A few comments:

  • perfect-forwarding is used, && and std::forward, to avoid potential copies if your type is large.
  • the type of the first argument is extracted by creating a std::tuple type and using std::tuple_element<0, ..>.
  • a static_assert is used, where each type is compared to the first type (via is_same).
  • I guess you want SFINAE to 'hide' this function unless the types are all identical. This is achieved via typename = std::enable_if_t< ..boolean-expression.. >
  • the class ... is essentially redundant. It's only purpose is to make it impossible for developers to cheat the checks by manually specifying the types (make_arr<int,char,size_t,bool>(..)). However, maybe this is too conservative - the static_assert will catch them anyway!

Upvotes: 2

user2807083
user2807083

Reputation: 2982

First of all, you'll need these includes:

#include <type_traits>
#include <tuple>

then, let us declare variadic template to detect whether types are same or not:

template <typename ... args>
struct all_same : public std::false_type {};


template <typename T>
struct all_same<T> : public std::true_type {};


template <typename T, typename ... args>
struct all_same<T, T, args...> : public all_same<T, args ... > {};

now we can use static_assert to detect if parameters types are same:

template <typename T, typename... args>
T* make_arr(args... arg) {

   // Code to check if passed args are of the same type
   static_assert(all_same<args ...>::value, "Params must have same types");

   T* arr = new T[sizeof...(arg)]{ arg... };
   return arr;
};

Finally, let us take return type of your function as first type of parameters - if all types are same we can take any of them. We use std::tuple for this

template <typename... args>
typename std::tuple_element<0, std::tuple<args...> >::type * make_arr(args... arg) {

   // Code to check if passed args are of the same type
   static_assert(all_same<args ...>::value, "Params must have same types");

   typedef typename std::tuple_element<0, std::tuple<args...> >::type T;

   T* arr = new T[sizeof...(arg)]{ arg... };
   return arr;
};

Upvotes: 3

Rakete1111
Rakete1111

Reputation: 49028

The only way I found is to make a helper function using SFINAE

//Basic function
template<typename T>
void allsame(T) {}

//Recursive function
template<typename T, typename T2, typename... Ts, 
typename = std::enable_if_t<std::is_same<T, T2>::value>>
void allsame(T arg, T2 arg2, Ts... args)
{
    allsame(arg2, args...);
}

You can then call it like so:

allsame(arg...);

The compiler will then throw an error if the types are not the same.


For 2), you could modfiy allsame to return the type. The only drawback to this function is that it won't work if the type isn't default constructable.

template<typename T>
T allsame(T) { return{}; }

T allsame(T arg, T2 arg2, Ts... args)

Then, you can decltype(allsame(args...)) to get the type

Upvotes: 4

Related Questions