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);
}
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). 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.
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. 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.
Any insights would be very welcome.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
template <int d> struct Vec {
double tab[d];
};
I want to be able to do (in C++0X):
Vec<3> a(1.,2.,3.,4.); // ignore the last argument, initialize tab
with {1.,2.,3.}
How can I write the constructor? (I know there is always exactly one
argument too many)
The only way I could think of is to replace Vec with:
template <int d> struct Vec {
double coor;
Vec<d-1> proj;
};
template <> struct Vec<0> {};
In this case, the constructor becomes easy to write recursively, but
operator[] becomes harder: either it is not constant time any more
(recursive implementation), or it relies on the way the structure
looks like in memory (something like (&coor)[i]), which may not be
perfectly portable.
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
Daniel Krügler wrote:
> On 19 Aug., 21:14, Marc <marc.gli...@gmail.com> wrote:
>> 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);
>> }
Note that the hom function was later replaced with:
template <class...> struct Last_type; // bug 39653
template <class T,class... U> struct Last_type<T,U...> {
typedef typename Last_type<U...>::type type;
};
template <class T> struct Last_type<T> {
typedef T type;
};
template <class T> T && last_arg(T && t) {
return std::forward<T>(t);
}
template <class T,class... U>
typename Last_type<U&&...>::type last_arg(T&&, U&&... u)
// bug 44175
//auto last_arg(T&&, U&&... u)
//->decltype(last_arg(std::forward<U>(u)...))
{
return last_arg(std::forward<U>(u)...);
}
in conjunction with an anti_move function to make sure I don't move
from a value that is still needed:
template <class T> typename std::remove_reference<T>::type const&
anti_move(T && t) { return t; }
> 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.
Indeed, now that you have written this, tuples look like they are the
right approach and I was wrong to avoid them. I guess I was afraid
they would add some overhead somewhere along the way, but there is no
reason the compiler can't inline and simplify everything. I was also
confused by the fact that an aggregate/array can't be initialized from
a tuple, but your code below shows how it is done. By the way, in the
follow-up post, the way I reimplement Vec is very similar to a tuple,
but I didn't think of using it as an intermediate.
One thing though: std::get seems to lose the rvalue-ness of arguments
(not that it's hard to get the i-th type and cast appropriately
afterwards).
>> With 4 arguments, it just fails because there are too many
>> initializers. If the language simply ignored extra initializers, it
>> would work
[...]
> 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.
At a first glance, it seems like it would only turn invalid code into
valid code, but I admit I haven't thought long about it, and if it is
not useful...
> template<std::size_t...> struct indices{};
Yes, the manipulations on indices you demonstrate look extremely
powerful.
> 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))...})
> {
> }
With code like this, do I get a conversion followed by a copy/move, or
can the compiler do the conversion directly into tab[i]? And if I
replace std::get with something more std::forward-like and pass a
temporary to the constructor, do I still stand a chance that tab[i]
may be directly move-initialized from it?
I would check this, but g++ is unable to handle this sort of code with
any non-trivial type T (it complains about a bad array initializer),
which makes it hard to test.
> 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.
I had forgotten one could add an extra unused template parameter for
metaprogramming purposes, which means I was still adding extra
arguments to the constructors instead (since you can't meta-program on
the return type). Thanks.
> 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.
Yes, I am using gcc snapshots (closest thing to a c++0x compiler), but
the absence of a real c++0x compiler (absolutely normal at this point)
makes it harder to learn.
You are welcome.
> Note that the hom function was later replaced with:
>
> template <class...> struct Last_type; // bug 39653
> template <class T,class... U> struct Last_type<T,U...> {
> typedef typename Last_type<U...>::type type;};
>
> template <class T> struct Last_type<T> {
> typedef T type;
> };
Occasionally I also found use for the more
general tool:
template<std::size_t I, class...> struct ith;
template<class T, class... Ts>
struct ith<0, T, Ts...> {
typedef T type;
};
template<std::size_t I, class T, class... Ts>
struct ith<I, T, Ts...> {
typedef typename ith<I - 1, Ts...>::type type;
};
> template <class T> T && last_arg(T && t) {
> return std::forward<T>(t);}
>
> template <class T,class... U>
> typename Last_type<U&&...>::type last_arg(T&&, U&&... u)
> // bug 44175
> //auto last_arg(T&&, U&&... u)
> //->decltype(last_arg(std::forward<U>(u)...))
> {
> return last_arg(std::forward<U>(u)...);
> }
Yes, that should also work. In fact, it is possible
to define a set of free get functions, tuple_element,
and tuple_size for parameter packs (I did that a while
ago). The disadvantage of such an approach is, that
the compiler needs to expand the pack recursively (with
one element less each time). So, I think that forwarding
such a pack once is *in general* the more effective
approach.
> One thing though: std::get seems to lose the rvalue-ness of arguments
> (not that it's hard to get the i-th type and cast appropriately
> afterwards).
Indeed, this is reflected by the following library issue:
http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#1191
> > 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))...})
> > {
> > }
>
> With code like this, do I get a conversion followed by a copy/move, or
> can the compiler do the conversion directly into tab[i]? And if I
> replace std::get with something more std::forward-like and pass a
> temporary to the constructor, do I still stand a chance that tab[i]
> may be directly move-initialized from it?
static_cast indeed performs a conversion and may construct a
temporary, see [expr.static.cast]/4. You can get rid of
the static_cast, but that would have the effect that you
couldn't initialize a double array with int values because
narrowing conversions strike back. Assuming that rvalue-
reference overloads of get are provided as described in LWG
issue #1191, you would probably want to replace this by:
template<std::size_t... I, class Tuple>
VecBase(eq_tag, indices<I...>, Tuple&& t) :
tab({static_cast<T>(std::get<I>(std::forward<Tuple>(t)))...})
{
}
If you can live without support for int -> double
support, you can remove the static_cast of-course,
but you should test that (when possible). In a more
complicated - but potentially more efficient -
implementation you could type-switch between
using static_cast (e.g. for conversions among
scalar types) and not using so (all other cases).
HTH & Greetings from Bremen,
Daniel Krügler