Glen Fletcher
Glen Fletcher

Reputation: 654

Multi patterend varadic templates in C++

I don't think this is possible based on what I've read however I'm hoping someone here may know of some solution that would get this to work.

I have a vector (maths) class for C++

template <typename T, size_t N> class vec;

And want to create a varadic friend function apply to apply a function to these vectors element-wise

i.e.

template <typename F, typename ...Args> friend vec<typename std::result_of<pow(Args&&...)>::type, N> apply(F&& f, const vec<Args, N>&... args);

which is valid (untested yet)

however I want to achieve a pattern like

template <typename F> friend vec<typename std::result_of<F&&(T&&)>::type, N> apply(F&& f, const vec<T, N>& V);
template <typename F> friend vec<typename std::result_of<F&&(T&&, T&&)>::type, N> apply(F&& f, const vec<T, N>& V1, const vec<T, N>& V2);
template <typename F> friend vec<typename std::result_of<F&&(T&&, T&&)>::type, N> apply(F&& f, const vec<T, N>& V1, const T& V2);
template <typename F> friend vec<typename std::result_of<F&&(T&&, T&&)>::type, N> apply(F&& f, const T& V1, const vec<T, N>& V2);
template <typename F, typename U> friend vec<typename std::result_of<F&&(T&&, U&&)>::type, N> apply(F&& f, const vec<T, N>& V1, const vec<U, N>& V2);
template <typename F, typename U> friend vec<typename std::result_of<F&&(T&&, U&&)>::type, N> apply(F&& f, const vec<T, N>& V1, const U& V2);
template <typename F, typename U> friend vec<typename std::result_of<F&&(U&&, T&&)>::type, N> apply(F&& f, const vec<U, N>& V1, const vec<T, N>& V2);
template <typename F, typename U> friend vec<typename std::result_of<F&&(U&&, T&&)>::type, N> apply(F&& f, const U& V1, const vec<T, N>& V2);

note that only one of the arguments is required to be a vector any scalars would be broadcasted to the length of the vector.

The idea is that apply(pow, /*vec<float,N>*/V, /*int*/n) -> {pow(V.v[i],n)...} where i -> 0 ... N rather than apply(pow, /*vec<float,N>*/V, /*int*/n) -> apply(pow, /*vec<float,N>*/V, /*vec<int,N>*/tmp{/*int*/n}) {pow(V.v[i],tmp.v[i])...}

So I would like to be able to write something like the following (which isn't valid C++, but it should give an idea of what I want to achieve)

template <typename F, typename ...Args> friend vec<typename std::result_of<pow(Args&&...)>::type, N> apply(F&& f, const vec<Args, N>&||scalar<Args>::type... args) {
    vec<typename std::result_of<pow(Args&&...)>::type, N> r;
    for (int i= 0; i < N; i++) { r = f((is_vec<Args>?args.v[i]:args)...); }
    return r;
}

EDIT:

Based on Frank's comments I'm looking for something along the lines of

template<typename F, typename ...Args, size_t N>
vec<typename std::enable_if<sum<is_vec<Args,N>...>::value > 0, std::result_of<F&&(base_type<Args>::type&&...)>::type>::type, N>
(F&& f, Args&&...args) {
    vec<typename std::result_of<F&&(base_type<Args>::type&&...)>::type, N> result;
    for(std::size_t i = 0 ; i < N ; ++i) { result.v[i] = f(extract_v(std::forward<Args>(args),i)...); }
    return result;
}

however I'm unsure if this version could even compile as it may be too ambiguous to be able to detriment the value of N.

Upvotes: 1

Views: 124

Answers (2)

max66
max66

Reputation: 66190

Not sure to understand what do you exactly want but...

It seems to me that can be useful a custom type traits to extract, from a list of types, the dimension of the Vec, iff (if and only if) in the list of types there is at least one Vec and there aren't Vec's of different lengths.

I suggest something as follows, heavily based on template specialization,

template <std::size_t, typename ...>
struct dimVec;

// ground case for no Vecs: unimplemented for SFINAE failure !
template <>
struct dimVec<0U>;

// ground case with one or more Vecs: size fixed
template <std::size_t N>
struct dimVec<N> : public std::integral_constant<std::size_t, N>
 { };

// first Vec: size detected
template <std::size_t N, typename T, typename ... Ts>
struct dimVec<0U, Vec<T, N>, Ts...> : public dimVec<N, Ts...>
 { };

// another Vec of same size: continue
template <std::size_t N, typename T, typename ... Ts>
struct dimVec<N, Vec<T, N>, Ts...> : public dimVec<N, Ts...>
 { };

// another Vec of different size: unimplemented for SFINAE failure !
template <std::size_t N1, std::size_t N2, typename T, typename ... Ts>
struct dimVec<N1, Vec<T, N2>, Ts...>;

// a not-Vec type: continue
template <std::size_t N, typename T, typename ... Ts>
struct dimVec<N, T, Ts...> : public dimVec<N, Ts...>
 { };

with the help of a template static variable

template <typename ... Args>
static constexpr auto dimVecV { dimVec<0U, Args...>::value };

Now should be easy.

You can write an apply() function that receive a variadic list of args of types Args... and is SFINAE enabled iff dimVecV<Args...> is defined

template <typename F, typename ... Args, std::size_t N = dimVecV<Args...>>
auto apply (F && f, Args ... as)
 { return applyH1(std::make_index_sequence<N>{}, f, as...); }

Observe that the N variable is used to SFINAE enable/disable the function but is useful itself: it's used to pass a std::index_sequence from 0 to N-1 to the first helper function applyH1()

template <std::size_t ... Is, typename F, typename ... Args>
auto applyH1 (std::index_sequence<Is...> const &, F && f, Args ... as)
   -> Vec<decltype(applyH2<0U>(f, as...)), sizeof...(Is)>
 { return { applyH2<Is>(f, as...)... }; }

that initialize the returned Vec with single values calculated from the second helper function applyH2()

template <std::size_t I, typename F, typename ... Args>
auto applyH2 (F && f, Args ... as)
 { return f(extrV<I>(as)...); }

that uses a set of template functions extrV()

template <std::size_t I, typename T, std::size_t N>
constexpr auto extrV (Vec<T, N> const & v)
 { return v[I]; }

template <std::size_t I, typename T>
constexpr auto extrV (T const & v)
 { return v; }

to extract the I-th element from a Vec or to pass-through a scalar value.

It's a little long but not particularly complicated.

The following is a full working example

#include <array>
#include <iostream>
#include <type_traits>

template <typename T, std::size_t N>
class Vec;

template <std::size_t, typename ...>
struct dimVec;

// ground case for no Vecs: unimplemented for SFINAE failure !
template <>
struct dimVec<0U>;

// ground case with one or more Vecs: size fixed
template <std::size_t N>
struct dimVec<N> : public std::integral_constant<std::size_t, N>
 { };

// first Vec: size detected
template <std::size_t N, typename T, typename ... Ts>
struct dimVec<0U, Vec<T, N>, Ts...> : public dimVec<N, Ts...>
 { };

// another Vec of same size: continue
template <std::size_t N, typename T, typename ... Ts>
struct dimVec<N, Vec<T, N>, Ts...> : public dimVec<N, Ts...>
 { };

// another Vec of different size: unimplemented for SFINAE failure !
template <std::size_t N1, std::size_t N2, typename T, typename ... Ts>
struct dimVec<N1, Vec<T, N2>, Ts...>;

// a not-Vec type: continue
template <std::size_t N, typename T, typename ... Ts>
struct dimVec<N, T, Ts...> : public dimVec<N, Ts...>
 { };

template <typename ... Args>
static constexpr auto dimVecV { dimVec<0U, Args...>::value };

template <std::size_t I, typename T, std::size_t N>
constexpr auto extrV (Vec<T, N> const & v)
 { return v[I]; }

template <std::size_t I, typename T>
constexpr auto extrV (T const & v)
 { return v; }

template <typename T, std::size_t N>
class Vec
 {
   private:
      std::array<T, N> d;

   public:
      template <typename ... Ts>
      Vec (Ts ... ts) : d{{ ts... }}
       { }

      T & operator[] (int i)
       { return d[i]; }

      T const & operator[] (int i) const
       { return d[i]; }
 };


template <std::size_t I, typename F, typename ... Args>
auto applyH2 (F && f, Args ... as)
 { return f(extrV<I>(as)...); }

template <std::size_t ... Is, typename F, typename ... Args>
auto applyH1 (std::index_sequence<Is...> const &, F && f, Args ... as)
   -> Vec<decltype(applyH2<0U>(f, as...)), sizeof...(Is)>
 { return { applyH2<Is>(f, as...)... }; }

template <typename F, typename ... Args, std::size_t N = dimVecV<Args...>>
auto apply (F && f, Args ... as)
 { return applyH1(std::make_index_sequence<N>{}, f, as...); }

long foo (int a, int b)
 { return a + b + 42; }

int main ()
 {
   Vec<int, 3U>  v3;
   Vec<int, 2U>  v2;

   auto r1 { apply(foo, v2, v2) };
   auto r2 { apply(foo, v3, v3) };
   auto r3 { apply(foo, v3, 0)  };

   static_assert( std::is_same<decltype(r1), Vec<long, 2U>>{}, "!" );
   static_assert( std::is_same<decltype(r2), Vec<long, 3U>>{}, "!" );
   static_assert( std::is_same<decltype(r3), Vec<long, 3U>>{}, "!" );

   // apply(foo, v2, v3); // compilation error
   // apply(foo, 1, 2);   // compilation error

 }

Upvotes: 1

user4442671
user4442671

Reputation:

You can achieve what you want through a combination of partial template specialization and parameter pack extension.

#include <array>

template<typename T, std::size_t N>
using Vec = std::array<T, N>;

template<typename T>
struct extract {
  static auto exec(T const& v, std::size_t) {return v;}
  enum { size = 1 };
};

template<typename T, std::size_t N>
struct extract<Vec<T,N>> {
  static auto exec(Vec<T,N> const& v, std::size_t i) {return v[i];}
  enum {size = N};
};

template<typename T>
auto extract_v(T const& v, std::size_t i) {return extract<T>::exec(v, i);}

template<typename... args>
struct extract_size {
    enum {size = 1};
};

template<typename first, typename... rest>
struct extract_size<first, rest...> {
    enum {
        rest_size_ = extract_size<rest...>::size,
        self_size_ = extract<first>::size,
        size = rest_size_ > self_size_ ? rest_size_ : self_size_ 
    };

    static_assert(self_size_ == 1 || rest_size_ == 1 || rest_size_ == self_size_, "");
};

template<typename F, typename... args_t>
auto apply(F const& cb, args_t&&... args) {
  constexpr std::size_t size = extract_size<std::decay_t<args_t>...>::size;
  using result_t = decltype(cb(extract_v(std::forward<args_t>(args),0)...));

  Vec<result_t, size> result;

  for(std::size_t i = 0 ; i < size ; ++i) {
    result[i] = cb(extract_v(std::forward<args_t>(args),i)...);
  }

  return result;
}

Upvotes: 0

Related Questions