4

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

Laith
  • 1,248
  • 2
  • 11
  • 19
  • not sure, but did you try `typeid` function. – seleciii44 May 15 '16 at 08:36
  • You want the `args` to be the same type *as each other*, and potentially different from `T`? Or you want them all to be the same type as `T`? Or you allow them to be different from each other, but simply require that it be possible to build your array of `T` with types that can convert to `T`? – Aaron McDaid May 15 '16 at 09:34

3 Answers3

4

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

Rakete1111
  • 47,013
  • 16
  • 123
  • 162
3

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;
};
user2807083
  • 2,962
  • 4
  • 29
  • 37
  • This does work but I didnt understand the last part of your answer which is the tuple part ... If you can briefly simplify it, I would be grateful ... +1 anyway – Laith May 15 '16 at 15:14
  • 1
    @Leo `std::tuple` is a standard variadic template, like `std::pair`, but with arbitrary types list. And there is `std::tuple_element` helper template which can be used for access to a tuple field or to type of tuple field by its index. In our case `std::tuple` represents list of template types, and we take type at index 0 using `std::tuple_element<...>::type`. As far templates arguments has same types any of that we can use as type to make pointer for holding array. – user2807083 May 16 '16 at 10:48
2

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!
Community
  • 1
  • 1
Aaron McDaid
  • 26,501
  • 9
  • 66
  • 88