Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Partial specialization of variadic value template

If I have a value templated class

template<int... values>
class A {};

Can I specialize it "easely" (without template recursion) for all sequences with a undetermined-length sequence of trailing 0's ?

// Pseudo code
template<int... values>
class A<values..., 0...> {};

Examples that should use the specialized version (in fact that should match every sequence eventually with zero trailing 0's):

A<1, 0>{}; // use overloaded version with overloaded template values = <1>
A<1, 1>{}; // use with values=<1, 1>
A<1, 2, 3, 0, 0, 0>{}; // use with template values = <1, 2, 3>
A<1, 2, 3>{}; // use with values=<1, 2, 3>
like image 378
rafoo Avatar asked Feb 01 '21 16:02

rafoo


People also ask

How to define the template parameter list of a partial specialization?

The template parameter list and the template argument list of a member of a partial specialization must match the parameter list and the argument list of the partial specialization. Just like with members of primary templates, they only need to be defined if used in the program.

What is the difference between primary and partial specialization?

Explicit (full) specialization of a member of a partial specialization is declared the same way as an explicit specialization of the primary template. If a primary template is a member of another class template, its partial specializations are members of the enclosing class template.

What is the difference between first partial specialization and first function template?

the first function template has the same template parameters as the first partial specialization and has just one function parameter, whose type is a class template specialization with all the template arguments from the first partial specialization

How to determine which specialization is more specialized in C++?

1) If only one specialization matches the template arguments, that specialization is used 2) If more than one specialization matches, partial order rules are used to determine which specialization is more specialized. The most specialized specialization is used, if it is unique (if it is not unique, the program cannot be compiled)


Video Answer


3 Answers

You can use constexpr functions to strip the trailing zeros from the end of the parameter pack:

template<int... values>
class A_impl {};

template<typename Arr>
constexpr auto num_trailing_zeros(Arr const& arr) {
    int count = 0;

    for (int i = arr.size() - 1; i >= 0; --i) {
        if (arr[i] != 0) return count;
        ++count;
    }

    return count;
}

// Not `constexpr` because we aren't providing a definition;
// we only care about the type.
// Marking it `constexpr` produces compilation warnings.
template<auto const& arr, std::size_t... Is>
auto A_from_arr(std::index_sequence<Is...>) -> A_impl<arr[Is]...>;

// Not `constexpr` because we need the `static constexpr` `arr` variable so
// that we can pass it as a reference argument.
// `static`s aren't allowed inside `constexpr` functions.
// This is fine, because we only care about the type.
template<int... values>
auto A_without_trailing_zeros() {
    static constexpr auto arr = std::array { values... };
    constexpr auto num_trailing = num_trailing_zeros(arr);
    return A_from_arr<arr>(std::make_index_sequence<sizeof...(values) - num_trailing>{});
}

template<int... values>
using A = decltype(A_without_trailing_zeros<values...>());

Demo

like image 127
Justin Avatar answered Oct 25 '22 07:10

Justin


Not sure about what do you exactly want... and surely you can't obtain what do you want in a simple way with a simple class A... but if you accept to add a level of indirection... I mean: a struct/class A<1, 2, 3, 0, 0, 0> that inherit from B<1, 2, 3>...

We need something that strip the trailing zero from an integer sequence.

Maybe there are simpler methods but I imagine a custom type traits as follows

template <typename, typename, int...>
struct szh;

// non zero element case    
template <int ... As, int ... Bs, int C, int ... Ds>
struct szh<std::integer_sequence<int, As...>,
           std::integer_sequence<int, Bs...>,
           C, Ds...>
 : public szh<std::integer_sequence<int, As..., Bs..., C>,
              std::integer_sequence<int>,
              Ds...>
 { };

// zero element case
template <int ... As, int ... Bs, int ... Ds>
struct szh<std::integer_sequence<int, As...>,
           std::integer_sequence<int, Bs...>,
           0, Ds...>
 : public szh<std::integer_sequence<int, As...>,
              std::integer_sequence<int, Bs..., 0>,
              Ds...>
 { };

// ground case
template <int ... As, int ... Bs>
struct szh<std::integer_sequence<int, As...>,
           std::integer_sequence<int, Bs...>>
 { using type = std::integer_sequence<int, As...>; };

template <int ... Is>
using strip_trailing_zeros
   = typename szh<std::integer_sequence<int>,
                  std::integer_sequence<int>,
                  Is...>::type;

I've made it generic because I think it's better make complicated code reusable. But if the final type is B<As...> instead of std::integer_sequence<int, As...>, the following code can be simplified a little but strip_trailing_zeros can't be re-used.

Now a simple class B (observe that the constructor prints the Is...)

template <int ... Is>
struct B
 { B () { ((std::cout << Is), ...); std::cout << '\n'; } };

and a converter (only declared) from std::integer_sequenc<int, Is...> to B<int...>

template <int ... Is>
B<Is...> foo (std::integer_sequence<int, Is...>);

so A become

template <int ... Is>
struct A : public decltype(foo(strip_trailing_zeros<Is...>{}))
 { };

The following is a full compiling example

#include <iostream>
#include <utility>

template <typename, typename, int...>
struct szh;

template <int ... As, int ... Bs, int C, int ... Ds>
struct szh<std::integer_sequence<int, As...>,
           std::integer_sequence<int, Bs...>,
           C, Ds...>
 : public szh<std::integer_sequence<int, As..., Bs..., C>,
              std::integer_sequence<int>,
              Ds...>
 { };

template <int ... As, int ... Bs, int ... Ds>
struct szh<std::integer_sequence<int, As...>,
           std::integer_sequence<int, Bs...>,
           0, Ds...>
 : public szh<std::integer_sequence<int, As...>,
              std::integer_sequence<int, Bs..., 0>,
              Ds...>
 { };

template <int ... As, int ... Bs>
struct szh<std::integer_sequence<int, As...>,
           std::integer_sequence<int, Bs...>>
 { using type = std::integer_sequence<int, As...>; };

template <int ... Is>
using strip_trailing_zeros
   = typename szh<std::integer_sequence<int>,
                  std::integer_sequence<int>,
                  Is...>::type;

template <int ... Is>
struct B
 { B () { ((std::cout << Is), ...); std::cout << '\n'; } };

template <int ... Is>
B<Is...> foo (std::integer_sequence<int, Is...>);

template <int ... Is>
struct A : public decltype(foo(strip_trailing_zeros<Is...>{}))
 { };

int main()
 {
   A<1, 0>{}; // print "1" (inherit from B<1>)
   A<1, 1>{}; // print "1, 1" (inherit from B<1, 1>)
   A<1, 2, 3, 0, 0, 0>{}; // print "1, 2, 3" (inherit from B<1, 2, 3>)
   A<1, 2, 3>{}; // // print "1, 2, 3" (inherit from B<1, 2, 3>)
 }
like image 21
max66 Avatar answered Oct 25 '22 08:10

max66


No, after-pack-deduction pattern matching does not work in C++.

Nothing is ever successfully matched after a pack is deduced. Ever.

A pattern-match can occur after a pack is expanded, but the pack cannot deduce types and then go on to match anything.

A seperate list is the best you can do.

like image 42
Yakk - Adam Nevraumont Avatar answered Oct 25 '22 07:10

Yakk - Adam Nevraumont