问题
This question is a follow on from my previous question Multi patterned varadic templates in C++ to which I received the solution:
#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
}
now lets say I also have a template class template <typename T, size_t N, size_t M> class mat;
which represents a NxM
matrix with the previous Vec
's being equivalent to columns (M
) for broadcasting, now I want to extend apply so it can also work with the Mat
class with out breaking handling of just vec
's.
Rules:
- if the argument list contains a
NxM
matrix then all matricesNxM
and all vectors must beM
, and the function will return aNxM
matrix - otherwise rules as previously for vectors, if the argument list
contains a
N
vector then all vectors must beN
, and the function will return aN
vector
As far as I can tell since the function f()
isn't implemented for matrices then the applyH2()
should fail (if not then it needs to), in its current form, thus apply would fail, hence it should be possible to implement another apply()
which accepts multiple sizes M
and N
in the same way, which will be used if the argument list contains a matrix, and then invoke extrV<I,J>(as)
which for vectors should be mapped as extrV<I,J>(as) -> extrV<J>(as)
this part seams pretty straight forward as dose implementing dimMatM
and dimMatN
in the same way as dimVec
and such that dimMatM
maps to dimVec
for vectors.
However I'm not sure how to go about getting the two versions on apply()
to work together with out error.
Optional: This part isn't really important by lets say that later I have a class tensor<T, N...>
with extends the concept on vec and mat to N dimensions, would it be possible to extend apply to work such that broadcasting occurs over the leading dimensions i.e.
double f(float x, float y);
int main() {
tensor<float, 2, 4, 6> f246;
tensor<int, 4, 6> i46;
tensor<float, 2, 4> f24;
auto r1 = apply(f, f246, i46); // returns tensor<double, 2, 4, 6>
// apply(f, f24, f246); // Compile ERROR: dimensions don't match
}
EDIT:
I just thought up a possible solution or at least part of it see below
template <size_t ...N>
struct tensor_helper {
template <size_t I, typename T, size_t ...M> static tensorGet(const tensor<T, M...>& V) { return V.v[I % product<M...>::value]; }
template <size_t I, typename T> static tensorGet(const T& V) { return V; }
template <std::size_t I, typename F, typename ...Args>
static auto applyH2 (F && f, Args ...as) -> decltype(f(std::declval<typename base_type<Args>::type>()...))
{ return f(tensorGet<I>(as)...); }
template <size_t ... Is, typename F, typename ... Args>
static auto applyH1(std::index_sequence<Is...> const &..., F && f, Args ... as) -> tensor<decltype(f(std::declval<typename base_type<Args>::type>()...)), N...>
{ return make_tensor<decltype(f(std::declval<typename base_type<Args>::type>()...)), N...>({applyH2<Is>(f, as...)... }); }
}
template <typename F, typename ...Args, size_t ...N = tensorSize<Args...> /* How do I get a list here? */>
auto apply (F && f, Args ... as)
{ return tensor_helper<N...>::applyH1(std::make_index_sequence<product<N...>>{}..., f, as...); }
the only problem is how to get tensorSize()
to provide a list of dims, all though I assume this could be manually expanded if just implemented for vectors and matrices, rather than using the parameter packs for dims.
EDIT 2:
I belive I can use constexpr array to variadic template, to solve the N
list problem, from my above code. But I'm too tried too actualy try implementing that concept tonight will have a try tomorrow.
回答1:
Conceptually is very similar but, with two dimensions, become a nightmare.
Instead of dimVec
, I propose a dimMat
with two dimensions (N
and M
) and SFINAE failure for unacceptable cases (different sizes vecs or mats, vect with size incompatible with mat); not sure that all case are managed but should be something as
template <std::size_t, std::size_t, typename ...>
struct dimMat;
// ground case for no Vecs and no Mats: unimplemented for SFINAE failure
template <>
struct dimMat<0U, 0U>;
// ground case with one or more Vecs and/or Mats: sizes fixed
template <std::size_t N, std::size_t M>
struct dimMat<N, M>
{
static constexpr std::size_t valN { N };
static constexpr std::size_t valM { M };
};
// first Vec: M detected
template <std::size_t M, typename T, typename ... Ts>
struct dimMat<0U, 0U, Vec<T, M>, Ts...> : public dimMat<0U, M, Ts...>
{ };
// first Mat: N and M detected
template <std::size_t N, std::size_t M, typename T, typename ... Ts>
struct dimMat<0U, 0U, Mat<T, N, M>, Ts...> : public dimMat<N, M, Ts...>
{ };
// first Mat after a correct Vect: N detected
template <std::size_t N, std::size_t M, typename T, typename ... Ts>
struct dimMat<0U, M, Mat<T, N, M>, Ts...> : public dimMat<N, M, Ts...>
{ };
// another Vec of correct size: continue
template <std::size_t N, std::size_t M, typename T, typename ... Ts>
struct dimMat<N, M, Vec<T, M>, Ts...> : public dimMat<N, M, Ts...>
{ };
// another Mat of correct sizes: continue
template <std::size_t N, std::size_t M, typename T, typename ... Ts>
struct dimMat<N, M, Mat<T, N, M>, Ts...> : public dimMat<N, M, Ts...>
{ };
// another Vec of different size: unimplemented for SFINAE failure
template <std::size_t N, std::size_t M1, std::size_t M2, typename T,
typename ... Ts>
struct dimMat<N, M1, Vec<T, M2>, Ts...>;
// another Mat of different sizes: unimplemented for SFINAE failure
template <std::size_t N1, std::size_t N2, std::size_t M1, std::size_t M2,
typename T, typename ... Ts>
struct dimMat<N1, M1, Mat<T, N2, M2>, Ts...>;
// a not-Vec, not-Mat type: continue
template <std::size_t N, std::size_t M, typename T, typename ... Ts>
struct dimMat<N, M, T, Ts...> : public dimMat<N, M, Ts...>
{ };
The helper template variables are simple
template <typename ... Args>
static constexpr auto dimMatN { dimMat<0U, 0U, Args...>::valN };
template <typename ... Args>
static constexpr auto dimMatM { dimMat<0U, 0U, Args...>::valM };
For apply()
, renamed applyM()
, I suppose something as
template <typename F, typename ... Args, std::size_t N = dimMatN<Args...>,
std::size_t M = dimMatM<Args...>>
auto applyM (F && f, Args ... as)
{ return applyMH1(std::make_index_sequence<N>{},
std::make_index_sequence<M>{},
f, as...); }
For applyMH1()
two cases: if N
is zero, so the first argument is an empty index list, we have the vec case
// Vec case: the first index list is empty: call applyH2()
template <std::size_t ... Js, typename F, typename ... Args>
auto applyMH1 (std::index_sequence<> const &,
std::index_sequence<Js...> const &, F && f, Args ... as)
-> Vec<decltype(applyH2<0U>(f, as...)), sizeof...(Js)>
{ return { applyH2<Js>(f, as...)... }; }
otherwise the mat case
template <std::size_t ... Is, std::size_t ... Js, typename F,
typename ... Args>
auto applyMH1 (std::index_sequence<Is...> const &,
std::index_sequence<Js...> const & js, F && f, Args ... as)
-> Mat<decltype(applyMH3<0U, 0U>(f, as...)), sizeof...(Is), sizeof...(Js)>
{ return {{{ applyMH2<Is>(js, f, as...) ... }}}; }
Now applyMH2()
template <std::size_t I, std::size_t ... Js, typename F, typename ... Args>
auto applyMH2 (std::index_sequence<Js...> const &, F && f, Args ... as)
-> std::array<decltype(applyMH3<0U, 0U>(f, as...)), sizeof...(Js)>
{ return {{ applyMH3<I, Js>(f, as...)... }}; }
and applyMH3()
template <std::size_t I, std::size_t J, typename F, typename ... Args>
auto applyMH3 (F && f, Args ... as)
{ return f(extrM<I, J>(as)...); }
given a extrM()
based on extrV()
template <std::size_t I, std::size_t J, typename T,
std::size_t N, std::size_t M>
constexpr auto extrM (Mat<T, N, M> const & v)
{ return v[I][J]; }
template <std::size_t I, std::size_t J, typename T>
constexpr auto extrM (T const & v)
{ return extrV<J>(v); }
The following is a full compiling example
#include <array>
#include <iostream>
#include <type_traits>
template <typename T, std::size_t N>
class Vec;
template <typename T, std::size_t N, std::size_t M>
struct Mat;
template <std::size_t, std::size_t, typename ...>
struct dimMat;
// ground case for no Vecs and no Mats: unimplemented for SFINAE failure
template <>
struct dimMat<0U, 0U>;
// ground case with one or more Vecs and/or Mats: sizes fixed
template <std::size_t N, std::size_t M>
struct dimMat<N, M>
{
static constexpr std::size_t valN { N };
static constexpr std::size_t valM { M };
};
// first Vec: M detected
template <std::size_t M, typename T, typename ... Ts>
struct dimMat<0U, 0U, Vec<T, M>, Ts...> : public dimMat<0U, M, Ts...>
{ };
// first Mat: N and M detected
template <std::size_t N, std::size_t M, typename T, typename ... Ts>
struct dimMat<0U, 0U, Mat<T, N, M>, Ts...> : public dimMat<N, M, Ts...>
{ };
// first Mat after a correct Vect: N detected
template <std::size_t N, std::size_t M, typename T, typename ... Ts>
struct dimMat<0U, M, Mat<T, N, M>, Ts...> : public dimMat<N, M, Ts...>
{ };
// another Vec of correct size: continue
template <std::size_t N, std::size_t M, typename T, typename ... Ts>
struct dimMat<N, M, Vec<T, M>, Ts...> : public dimMat<N, M, Ts...>
{ };
// another Mat of correct sizes: continue
template <std::size_t N, std::size_t M, typename T, typename ... Ts>
struct dimMat<N, M, Mat<T, N, M>, Ts...> : public dimMat<N, M, Ts...>
{ };
// another Vec of different size: unimplemented for SFINAE failure
template <std::size_t N, std::size_t M1, std::size_t M2, typename T,
typename ... Ts>
struct dimMat<N, M1, Vec<T, M2>, Ts...>;
// another Mat of different sizes: unimplemented for SFINAE failure
template <std::size_t N1, std::size_t N2, std::size_t M1, std::size_t M2,
typename T, typename ... Ts>
struct dimMat<N1, M1, Mat<T, N2, M2>, Ts...>;
// a not-Vec, not-Mat type: continue
template <std::size_t N, std::size_t M, typename T, typename ... Ts>
struct dimMat<N, M, T, Ts...> : public dimMat<N, M, Ts...>
{ };
template <typename ... Args>
static constexpr auto dimMatN { dimMat<0U, 0U, Args...>::valN };
template <typename ... Args>
static constexpr auto dimMatM { dimMat<0U, 0U, Args...>::valM };
template <std::size_t, typename ...>
struct dimVec;
// ground case for no Vecs: unimplemented !
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 !
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 <std::size_t I, typename T, std::size_t N, std::size_t M>
constexpr auto extrV (Mat<T, N, M> const & v)
{ return 0; }
template <std::size_t I, std::size_t J, typename T,
std::size_t N, std::size_t M>
constexpr auto extrM (Mat<T, N, M> const & v)
{ return v[I][J]; }
template <std::size_t I, std::size_t J, typename T>
constexpr auto extrM (T const & v)
{ return extrV<J>(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 <typename T, std::size_t N, std::size_t M>
struct Mat
{
std::array<std::array<T, M>, N> m;
auto & operator[] (int i)
{ return m[i]; }
auto const & operator[] (int i) const
{ return m[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...); }
template <std::size_t I, std::size_t J, typename F, typename ... Args>
auto applyMH3 (F && f, Args ... as)
{ return f(extrM<I, J>(as)...); }
template <std::size_t I, std::size_t ... Js, typename F, typename ... Args>
auto applyMH2 (std::index_sequence<Js...> const &, F && f, Args ... as)
-> std::array<decltype(applyMH3<0U, 0U>(f, as...)), sizeof...(Js)>
{ return {{ applyMH3<I, Js>(f, as...)... }}; }
// Vec case: the first index list is empty: call applyH2()
template <std::size_t ... Js, typename F, typename ... Args>
auto applyMH1 (std::index_sequence<> const &,
std::index_sequence<Js...> const &, F && f, Args ... as)
-> Vec<decltype(applyH2<0U>(f, as...)), sizeof...(Js)>
{ return { applyH2<Js>(f, as...)... }; }
template <std::size_t ... Is, std::size_t ... Js, typename F,
typename ... Args>
auto applyMH1 (std::index_sequence<Is...> const &,
std::index_sequence<Js...> const & js, F && f, Args ... as)
-> Mat<decltype(applyMH3<0U, 0U>(f, as...)), sizeof...(Is), sizeof...(Js)>
{ return {{{ applyMH2<Is>(js, f, as...) ... }}}; }
template <typename F, typename ... Args, std::size_t N = dimMatN<Args...>,
std::size_t M = dimMatM<Args...>>
auto applyM (F && f, Args ... as)
{ return applyMH1(std::make_index_sequence<N>{},
std::make_index_sequence<M>{},
f, as...); }
long foo (int a, int b)
{ return a + b + 42; }
int main ()
{
Vec<int, 3U> v3;
Vec<int, 2U> v2;
Mat<int, 2U, 3U> m23;
Mat<int, 2U, 4U> m24;
auto r1 { applyM(foo, v2, v2) };
auto r2 { applyM(foo, v3, v3) };
auto r3 { applyM(foo, v3, 0) };
auto r4 { applyM(foo, v3, m23) };
auto r5 { applyM(foo, m24, 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>>{}, "!" );
static_assert( std::is_same<decltype(r4), Mat<long, 2U, 3U>>{}, "!" );
static_assert( std::is_same<decltype(r5), Mat<long, 2U, 4U>>{}, "!" );
//applyM(foo, v2, v3); // compilation error
//applyM(foo, 1, 2); // compilation error
//applyM(foo, v2, m23); // compilation error
//applyM(foo, m24, m23); // compilation error
}
回答2:
If you're still interested in a variadic N dimensional tensor<>
based solution... well... it's a nightmare but a nightmare of different type.
First of all, I've prepared the following recursive wrapper for std::array
template <typename T, std::size_t ...>
struct tensor;
template <typename T, std::size_t N, std::size_t ... Ns>
struct tensor<T, N, Ns...>
{
using nextT = std::conditional_t<(sizeof...(Ns) > 0U),
tensor<T, Ns...>, T>;
std::array<nextT, N> value;
auto const & operator[] (std::size_t i) const
{ return value[i]; }
auto & operator[] (std::size_t i)
{ return value[i]; }
};
The extrV()
function is now recursive and accept the list of index in a std::index_sequence
// scalar case: return value
template <std::size_t ... Is, typename T>
constexpr auto extrV (std::index_sequence<Is...> const &, T const & t)
{ return t; }
// tensor case with lower dimension: skip the first requested index
template <std::size_t I0, std::size_t ... Is,
typename T, std::size_t ... Js>
constexpr auto extrV
(std::index_sequence<I0, Is...> const &, tensor<T, Js...> const & t,
std::enable_if_t<(sizeof...(Is) >= sizeof...(Js))> * = nullptr)
{ return extrV(std::index_sequence<Is...>{}, t); }
// tensor case with exact dimension: use the first requested index
template <std::size_t I0, std::size_t ... Is,
typename T, std::size_t ... Js>
constexpr auto extrV
(std::index_sequence<I0, Is...> const &, tensor<T, Js...> const & t,
std::enable_if_t<(sizeof...(Is)+1U == sizeof...(Js))> * = nullptr)
{ return extrV(std::index_sequence<Is...>{}, t[I0]); }
Now the custom type traits that, given a variadic list of Args...
types, extract the longest common sequence of indexes (if any) and return it inside a std::index_sequence
(in case)
template <typename ...>
struct commonDims;
// ground case: define type as surviving parameter
template <std::size_t I0, std::size_t ... Is>
struct commonDims<std::index_sequence<I0, Is...>>
{ using type = std::index_sequence<I0, Is...>; };
// no tensor type: continue
template <typename IS, typename T0, typename ... Ts>
struct commonDims<IS, T0, Ts...> : public commonDims<IS, Ts...>
{ };
// tensor type: continue with bigger common index list (if any)
template <std::size_t ... Is, typename T, std::size_t ... Js,
typename ... Ts>
struct commonDims<std::index_sequence<Is...>, tensor<T, Js...>, Ts...>
: public commonDims<greaterSeqT<std::index_sequence<Is...>,
std::index_sequence<Js...>,
(sizeof...(Is) > sizeof...(Js))>, Ts...>
{ };
template <typename ... Ts>
using commonDimsT = typename commonDims<std::index_sequence<>, Ts...>::type;
As you can see, it uses a greaterSeq
helper type traits
template <typename, typename, bool>
struct greaterSeq;
template <typename T1, typename T2>
struct greaterSeq<T1, T2, true> : public gsHelper<T1, T1, T2>
{ };
template <typename T1, typename T2>
struct greaterSeq<T1, T2, false> : public gsHelper<T2, T2, T1>
{ };
template <typename T1, typename T2, bool B>
using greaterSeqT = typename greaterSeq<T1, T2, B>::type;
that uses another gsHelper
helper type traits
template <typename, typename, typename, typename = std::true_type>
struct gsHelper;
// sequences of different lengths: skipp the first index in longest
template <typename T, std::size_t I0, std::size_t ... Is, std::size_t ... Js>
struct gsHelper<T, std::index_sequence<I0, Is...>,
std::index_sequence<Js...>,
std::integral_constant<bool, (sizeof...(Is) >= sizeof...(Js))>>
: public gsHelper<T, std::index_sequence<Is...>,
std::index_sequence<Js...>>
{ };
template <typename T, typename IS>
struct gsHelper<T, IS, IS>
{ using type = T; };
Now the apply()
function
template <typename F, typename ... Args, typename IS = commonDimsT<Args...>>
auto apply (F && f, Args const & ... as)
{ return applyH1(std::index_sequence<>{}, IS{}, f, as...); }
that start a recursion of call between applyH1()
and applyH2()
functions
template <std::size_t ... Is, std::size_t ... Js, std::size_t ... Ks,
typename F, typename ... Args>
auto applyH2 (std::index_sequence<Is...> const &,
std::index_sequence<Js...> const &,
std::index_sequence<Ks...> const & ks,
F && f, Args const & ... as)
-> tensor<
decltype(call(std::index_sequence<(Is, 0U)..., 0U, (Ks, 0U)...>{},
f, as...)), sizeof...(Js), Ks...>
{ return {{{ applyH1(std::index_sequence<Is..., Js>{},
ks, f, as...) ... }}}; }
template <typename IS, typename F, typename ... Args>
auto applyH1 (IS const & is, std::index_sequence<> const &,
F && f, Args const & ... as)
{ return call(is, f, as...); }
template <typename IS, std::size_t J0, std::size_t ... Js,
typename F, typename ... Args>
auto applyH1 (IS const & is, std::index_sequence<J0, Js...> const &,
F && f, Args const & ... as)
{ return applyH2(is, std::make_index_sequence<J0>{},
std::index_sequence<Js...>{}, f, as...); }
that terminate with a call to call()
template <typename IS, typename F, typename ... Args>
auto call (IS const & is, F && f, Args const & ... as)
{ return f(extrV(is, as)...); }
The following is a full compiling example
#include <array>
#include <string>
#include <iostream>
template <typename T, std::size_t ...>
struct tensor;
template <typename T, std::size_t N, std::size_t ... Ns>
struct tensor<T, N, Ns...>
{
using nextT = std::conditional_t<(sizeof...(Ns) > 0U),
tensor<T, Ns...>, T>;
std::array<nextT, N> value;
auto const & operator[] (std::size_t i) const
{ return value[i]; }
auto & operator[] (std::size_t i)
{ return value[i]; }
};
// scalar case: return value
template <std::size_t ... Is, typename T>
constexpr auto extrV (std::index_sequence<Is...> const &, T const & t)
{ return t; }
// tensor case with lower dimension: skip the first requested index
template <std::size_t I0, std::size_t ... Is,
typename T, std::size_t ... Js>
constexpr auto extrV
(std::index_sequence<I0, Is...> const &, tensor<T, Js...> const & t,
std::enable_if_t<(sizeof...(Is) >= sizeof...(Js))> * = nullptr)
{ return extrV(std::index_sequence<Is...>{}, t); }
// tensor case with exact dimension: use the first requested index
template <std::size_t I0, std::size_t ... Is,
typename T, std::size_t ... Js>
constexpr auto extrV
(std::index_sequence<I0, Is...> const &, tensor<T, Js...> const & t,
std::enable_if_t<(sizeof...(Is)+1U == sizeof...(Js))> * = nullptr)
{ return extrV(std::index_sequence<Is...>{}, t[I0]); }
template <typename, typename, typename, typename = std::true_type>
struct gsHelper;
// sequences of different lengths: skipp the first index in longest
template <typename T, std::size_t I0, std::size_t ... Is, std::size_t ... Js>
struct gsHelper<T, std::index_sequence<I0, Is...>,
std::index_sequence<Js...>,
std::integral_constant<bool, (sizeof...(Is) >= sizeof...(Js))>>
: public gsHelper<T, std::index_sequence<Is...>,
std::index_sequence<Js...>>
{ };
template <typename T, typename IS>
struct gsHelper<T, IS, IS>
{ using type = T; };
template <typename, typename, bool>
struct greaterSeq;
template <typename T1, typename T2>
struct greaterSeq<T1, T2, true> : public gsHelper<T1, T1, T2>
{ };
template <typename T1, typename T2>
struct greaterSeq<T1, T2, false> : public gsHelper<T2, T2, T1>
{ };
template <typename T1, typename T2, bool B>
using greaterSeqT = typename greaterSeq<T1, T2, B>::type;
template <typename ...>
struct commonDims;
// ground case: define type as surviving parameter
template <std::size_t I0, std::size_t ... Is>
struct commonDims<std::index_sequence<I0, Is...>>
{ using type = std::index_sequence<I0, Is...>; };
// no tensor type: continue
template <typename IS, typename T0, typename ... Ts>
struct commonDims<IS, T0, Ts...> : public commonDims<IS, Ts...>
{ };
// tensor type: continue with bigger common index list (if any)
template <std::size_t ... Is, typename T, std::size_t ... Js,
typename ... Ts>
struct commonDims<std::index_sequence<Is...>, tensor<T, Js...>, Ts...>
: public commonDims<greaterSeqT<std::index_sequence<Is...>,
std::index_sequence<Js...>,
(sizeof...(Is) > sizeof...(Js))>, Ts...>
{ };
template <typename ... Ts>
using commonDimsT = typename commonDims<std::index_sequence<>, Ts...>::type;
template <typename IS, typename F, typename ... Args>
auto call (IS const & is, F && f, Args const & ... as)
{ return f(extrV(is, as)...); }
template <std::size_t ... Is, std::size_t ... Js, std::size_t ... Ks,
typename F, typename ... Args>
auto applyH2 (std::index_sequence<Is...> const &,
std::index_sequence<Js...> const &,
std::index_sequence<Ks...> const & ks,
F && f, Args const & ... as)
-> tensor<
decltype(call(std::index_sequence<(Is, 0U)..., 0U, (Ks, 0U)...>{},
f, as...)), sizeof...(Js), Ks...>
{ return {{{ applyH1(std::index_sequence<Is..., Js>{},
ks, f, as...) ... }}}; }
template <typename IS, typename F, typename ... Args>
auto applyH1 (IS const & is, std::index_sequence<> const &,
F && f, Args const & ... as)
{ return call(is, f, as...); }
template <typename IS, std::size_t J0, std::size_t ... Js,
typename F, typename ... Args>
auto applyH1 (IS const & is, std::index_sequence<J0, Js...> const &,
F && f, Args const & ... as)
{ return applyH2(is, std::make_index_sequence<J0>{},
std::index_sequence<Js...>{}, f, as...); }
template <typename F, typename ... Args, typename IS = commonDimsT<Args...>>
auto apply (F && f, Args const & ... as)
{ return applyH1(std::index_sequence<>{}, IS{}, f, as...); }
long foo (int a, int b)
{ return a + b + 42; }
int main ()
{
tensor<int, 2, 3, 4, 5> t0;
t0[0][0][0][0] = 1;
using t1 = commonDimsT<tensor<int, 1>, long, tensor<long, 3, 2, 1>, int>;
static_assert(std::is_same<t1, std::index_sequence<3, 2, 1>>{}, "!");
auto r1 { apply(foo, tensor<int, 3, 2, 1>{}, 0) };
auto r2 { apply(foo, tensor<int, 3, 2, 1>{}, tensor<int, 3, 2, 1>{}) };
auto r3 { apply(foo, tensor<int, 3, 2, 1>{}, tensor<int, 2, 1>{}) };
auto r4 { apply(foo, tensor<int, 3, 2, 1>{}, tensor<int, 1>{}) };
auto r5 { apply(foo, 0, tensor<int, 1>{}) };
static_assert(std::is_same<decltype(r1), tensor<long, 3, 2, 1>>{}, "!");
static_assert(std::is_same<decltype(r2), tensor<long, 3, 2, 1>>{}, "!");
static_assert(std::is_same<decltype(r3), tensor<long, 3, 2, 1>>{}, "!");
static_assert(std::is_same<decltype(r4), tensor<long, 3, 2, 1>>{}, "!");
static_assert(std::is_same<decltype(r5), tensor<long, 1>>{}, "!");
// compilation errors (no common tensor)
//apply(foo, 0, 0);
//apply(foo, tensor<int, 3, 2, 1>{}, tensor<int, 2>{});
}
来源:https://stackoverflow.com/questions/50322241/extending-multi-patterned-variadic-templates-in-c