3

It is possible to use template parameters pack as follows:

template <int T1, int... Ts>
struct Test {
  static constexpr int sizes[] = {Ts...};
};

template <int T1, int... Ts>
constexpr int Test<T1, Ts...>::sizes[];

However, as it is detailed here, the template parameter pack must be the last template parameter. Hence, we cannot have a code such as this:

template <int T1, int... Ts, int Tn>
struct Test {
  static constexpr int sizes[] = {Ts...}; 
  Foo<Ts...> foo;
};

template <int T1, int... Ts, int Tn>
constexpr int Test<T1, Ts..., Tn>::sizes[];

In many cases, we need to have access to the last element of a set of template parameters. My question is, what's the best practice for realizing the above code?

Edit: This is not duplicate of this question. I am trying to get everything except the last parameter (not the last parameter itself), since I need to define Foo as follows:

  Foo<Ts...> foo;
MTMD
  • 1,162
  • 2
  • 11
  • 23
  • 1
    What exactly do you mean by "efficient" here? least amount of code? fastest to compile? something else? – Michael Kenzel Apr 02 '19 at 02:35
  • @MichaelKenzel. Amount of code doesn't matter. I would very much like it to be compile time computable. – MTMD Apr 02 '19 at 02:36

3 Answers3

3

You could go with the standard method of using std::index_sequence

template<template<auto...> typename Tmp, size_t... Is, typename... Args>
constexpr auto take_as(std::index_sequence<Is...>, Args...)
{
    using Tup = std::tuple<Args...>;
    return Tmp<std::tuple_element_t<Is, Tup>{}...>{};
}

template<auto... Vals>
struct except_last
{
    template<template<auto...> typename Tmp>
    using as = decltype(take_as<Tmp>(std::make_index_sequence<sizeof...(Vals) - 1>{},
                                     std::integral_constant<decltype(Vals), Vals>{}...));
};

Which you use as

using F = except_last<1, 2, 3, 4>::as<Foo>;  // F is Foo<1, 2, 3>

This is both easier to implement and read, but you potentially get O(n) instantiation depth. If you are obsessed with efficiency, you could do O(1) instantiation depth by abusing fold expressions

template<typename T>
struct tag
{
    using type = T;
};

template<typename F, typename... Ts>
using fold_t = decltype((F{} + ... + tag<Ts>{}));

template<size_t N, typename... Ts>
struct take
{    
    template<typename T>
    auto operator+(tag<T>) -> take<N - 1, Ts..., T>;
};

template<typename... Ts>
struct take<0, Ts...>
{
    template<template<auto...> typename Tmp>
    using as = Tmp<Ts{}...>;

    template<typename T>
    auto operator+(tag<T>) -> take<0, Ts...>;
};

template<auto... Vals>
struct except_last
{
    template<template<auto...> typename Tmp>
    using as = fold_t<take<sizeof...(Vals) - 1>,
                      std::integral_constant<decltype(Vals), Vals>...>::template as<Tmp>;
};
Passer By
  • 19,325
  • 6
  • 49
  • 96
  • Can't you replace the `std::integral_constant...` with `std::forward(Vals)...` or `std::forward_as_tuple(Vals...)`? – Caleth Apr 02 '19 at 08:41
  • @Caleth The point is to embed values in types, so no. The reason behind this embedding is to forward the "constexpr-ness" down the call stack, we can't have `constexpr` parameters as values. – Passer By Apr 02 '19 at 08:43
0

What's the most efficient way to access the last template parameter?

You could use a little helper to convert the parameter pack into an array.

template<int... Args>
struct pack {
    static constexpr std::array as_array{ Args... };
};

You can then get the last argument with array indexing:

template <int T1, int... Ts>
struct Test {
    static constexpr int last = pack<Ts...>::as_array[sizeof...(Ts) - 1];
eerorika
  • 232,697
  • 12
  • 197
  • 326
  • Brilliant. Can I get everything except the last parameter? – MTMD Apr 02 '19 at 02:30
  • @MTMD You can access all elements through the array using their indices. But if you want a parameter pack containing everything except last element, then this approach might not be ideal. – eerorika Apr 02 '19 at 02:33
  • That's exactly what I want (to call `Foo`). Can you please suggest something? – MTMD Apr 02 '19 at 02:34
  • @MTMD I would assume that a recursive template can work, as it does for pretty much anything parameter pack related, but there is probably a better approach. If [p0535r0](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0535r0.html) is adopted to the standard, this would become trivial :) – eerorika Apr 02 '19 at 02:54
  • Exactly :). Thank you. I think I'll go with recursive template. – MTMD Apr 02 '19 at 02:57
0

integer_sequence is a way:

template <typename SeqN, typename Seq> struct TestImpl;

template <int... Ns, std::size_t ... Is>
struct TestImpl<std::integer_sequence<int, Ns...>, std::index_sequence<Is...>>
{
private:
    using SeqTuple = std::tuple<std::integral_constant<int, Ns>...>;
public:
  static constexpr int sizes[] = {std::tuple_element_t<Is, SeqTuple>::value...}; 
  Foo<std::tuple_element_t<Is, SeqTuple>::value...> foo;
};


template <int N1, int N2, int... Ns> // At least 2 numbers
using Test = TestImpl<std::integer_sequence<int, N1, N2, Ns...>,
                      std::make_index_sequence<1 + sizeof...(Ns)>>;

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302