Reputation: 654
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
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
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