Re: argument packs manipulations
On 19 Aug., 21:14, Marc <marc.gli...@gmail.com> wrote:
Hello,
I am trying to make a class that acts like the following, but in any
dimension:
template <typename T>
struct Vec3 {
T tab[3];
Vec3(T const& t1,T const& t2):tab({t1,t2,1}){}
Vec3(T const& t1,T const& t2,T const& t3):tab({t1,t2,t3}){}
Vec3(T const& t1,T const& t2,T const& t3,T const& t4)
:tab({t1/t4,t2/t4,t3/t4}) {}
};
My first try is:
template <typename T,int d>
struct Vec {
T tab[d];
template <class... U>
Vec(U... u):tab({u...}){
if(sizeof...(u)==d-1) tab[d-1]=1;
if(sizeof...(u)==d+1)
for(int i=0;i<d;++i)
tab[i]/=hom({static_cast<T>(u)...});
}
};
with the helper function:
template <class T>
T const& hom(std::initializer_list<T> t){
return *(t.end()-1);
}
A more natural way to define such a function is to
use
template<class... T>
inline std::tuple<T&&...> forward_as_tuple(T&&... t) {
return std::tuple<T&&...>(std::forward<T>(t)...);
}
as a means to pack your arguments and then to deduce the
very last tuple index via the official tuple API, e.g.
std::get<sizeof...(u) - 1>(forward_as_tuple(std::forward<U>(u)...));
The advantage versus your approach is, that no copying takes
place, just a repacking of the arguments as temporary
references to the arguments.
Vec<T,3> can be initialized with 3 arguments just fine.
With 2 arguments, it works, though the last T is first default
initialized and then affected, instead of being directly initialized
to 1, but that might be fixable.
With 4 arguments, it just fails because there are too many
initializers. If the language simply ignored extra initializers, it
would work (though not as well as the Vec3 version, but then replacing
"u..." with "u/something..." should help if the rest is solved).
This sounds as if you would recommend that the language should
be changed that way. I would feel strongly concerned about such
a change. It is possible to realize what you want with pure
C++0x means without such an "extension", see below.
Any idea how I can handle this? (I could forget about doing a clean
initialization of the array and simply affect values in a for loop,
but that would be disappointing and slower (any operation on the type
T is expensive)).
The helper function is not very nice, is there a way I could remove
it? I could improve it by writing a recursive function that calls
itself with one less argument each time (the first one) and returns it
when there is only one left, but that's still not very nice.
Use the combination of forward_as_tuple and the tuple API
to realize that.
It looks like the checking on the number of arguments of the
constructor has to be done inside the constructor and I can't use
overloading and/or SFINAE.
This is incorrect, see below to which extend that can
be realized.
Or maybe I could do something like:
Vec(U... u)
:somebaseclassordelegatingconstructor(integral_constant<int,sizeof...
(u)>,u...)
and overload there, which would solve most problems but not the most
important one: I can't remove the last parameter from an expansion
pack.
This is the way to go - and it allows to remove the last parameter
(or whatever parameter).
Any insights would be very welcome.
Assume you have the following tuple/variadic helper
types and functions:
#include <cstddef>
#include <utility>
#include <type_traits>
#include <tuple>
template<std::size_t...> struct indices{};
template<std::size_t I, class IndexTuple, std::size_t N>
struct make_indices_impl;
template<std::size_t I, std::size_t... Indices, std::size_t N>
struct make_indices_impl<I, indices<Indices...>, N>
{
typedef typename make_indices_impl<I + 1,
indices<Indices..., I>, N>::type type;
};
template<std::size_t N, std::size_t... Indices>
struct make_indices_impl<N, indices<Indices...>, N> {
typedef indices<Indices...> type;
};
template<std::size_t N>
struct make_indices : make_indices_impl<0, indices<>, N> {};
template<class... T>
inline std::tuple<T&&...> forward_as_tuple(T&&... t) {
return std::tuple<T&&...>(std::forward<T>(t)...);
}
we can solve your problem by introducing the following
use-case specific types:
struct eq_tag{}; // D == sizeof...(U)
struct le_tag{}; // D + 1 == sizeof...(U)
struct gr_tag{}; // D - 1 == sizeof...(U)
// Signum: (int) sizeof...(U) - D
template<int Signum> struct tagger_impl;
template<> struct tagger_impl<0> { typedef eq_tag type; };
template<> struct tagger_impl<-1> { typedef le_tag type; };
template<> struct tagger_impl<+1> { typedef gr_tag type; };
template<int N, int D> struct tagger : tagger_impl<N - D>{};
template<class T, int D>
struct VecBase {
T tab[D];
template<std::size_t... I, class Tuple>
VecBase(eq_tag, indices<I...>, Tuple&& t) :
tab({static_cast<T>(std::get<I>(t))...})
{
}
template<std::size_t... I, class Tuple>
VecBase(le_tag, indices<I...>, Tuple&& t) :
tab({static_cast<T>(std::get<I>(t))..., 1})
{
}
template<std::size_t... I, class Tuple>
VecBase(gr_tag, indices<I...>, Tuple&& t) :
tab({static_cast<T>(std::get<I>(t))/std::get<D>(t)...})
{
}
};
and finally your template Vec:
template<class T, int D>
struct Vec : VecBase<T, D> {
template<class... U,
class = typename std::enable_if<
sizeof...(U) >= D - 1 && sizeof...(U) <= D + 1
>::type
>
Vec(U&&... u) : VecBase<T, D>(typename tagger<sizeof...(U),
D>::type(),
typename make_indices<sizeof...(U) < D ? sizeof...(U) :
D>::type(),
forward_as_tuple(std::forward<U>(u)...))
{}
};
Above usage of std::enable_if shows how constraining of
the parameter pack is possible. With a little more work,
you can add even more constraints that e.g. reject any pack,
where not all parameters U are convertible to T.
The usage of the base class is actually not necessary in
C++0x. Via delegating constructors, this could all be done
by adding the three constructors from the base class as
private target constructors within Vec. For practical tests,
this feature will not be available before gcc 4.6 though.
HTH & Greetings from Bremen,
Daniel Kr?gler
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]