Re: argument packs manipulations
First, thanks a lot for this very detailed and useful answer.
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.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]