6

I'd like to use partial template specialization in order to 'break down' an array (which is created at compile time) into a parameter pack composed of its values (to interface with other structures I define in my code). The following (my first attempt) is not compiling

#include <array>

template <typename T, auto k> struct K;
template <typename T, std::size_t... A> struct K<T, std::array<std::size_t, sizeof...(A)>{A...}> {};

because the template argument std::array<long unsigned int, sizeof... (A)>{A ...} must not involve template parameters. As I understood it, it is not possible to provide non type parameters in a partial template specialization if they non-trivially depend on template parameters. Hence I attempted to work around this issue by containing the value in a type:

#include <array>

template <auto f> struct any_type;

template <typename T, typename array_wrapper> struct FromArr;
template <typename T, std::size_t... A>
struct FromArr<T, any_type<std::array<std::size_t, sizeof...(A)>{A...}>> {};

int main() {
  FromArr<int, any_type<std::array<std::size_t, 2>{1, 2}>> d;
  (void) d;
}

However, here, the partial template specialization fails when I'm trying to use it; the definition above does not match the way I use it, and I am unsure why. It fails with the following error:

file.cc: In function ‘int main()’:
file.cc:10:55: error: aggregate ‘FromArr<int, Any<std::array<long unsigned int, 2>{std::__array_traits<long unsigned int, 2>::_Type{1, 2}}> > d’ has incomplete type and cannot be defined
  10  |   FromArr<int, Any<std::array<std::size_t, 2>{1, 2}>> d;

Is it possible to work around this / use a different approach in order to interface the array as parameter pack?

Used Compiler

I use g++-10.0 (GCC) 10.0.1 20200124 (experimental) and compile via g++ -std=c++2a file.cc, c++2a is required because I use non-type template parameters.

Edit:

Description how the array is ment to be processed

In my real code I have got a structure which depends on -- among others -- a parameter pack (1). It would be nice if I were able to use an array (2) (which I have got in another piece of my code as a non-type template argument) to interface with that structure, as sketched in the code below.

template <int... s> struct toBeUsed;                               // (1)
template <std::size_t s, std::array<int, s> arr> struct Consumer { // (2)
    toBeUsed<arr> instance; // This is what I would like to do
}

My Attempt is to write a helper struct as above FromStruct, which I can instantiate with an array in which I have a typedef FromStruct::type that provides toBeUsed with the correct arguments, similar to this example, which does what I want to do here with the types a std::tuple is composed of.

Link to the example

here I link the simplified usage example (2nd code block).

mutableVoid
  • 1,284
  • 2
  • 11
  • 29
  • 1
    Describing (with a little bit of code) how the array is meant to be processed will likely help you get an adequate workaround. There is no telling currently what suggestion will best solve your problem. – StoryTeller - Unslander Monica Feb 27 '20 at 13:18
  • 1
    Possibly related: [C++ constexpr : Compute a std array at compile time](https://stackoverflow.com/questions/49097211/c-constexpr-compute-a-std-array-at-compile-time). – dfrib Feb 27 '20 at 13:18
  • @dfri Thanks for the link, this implementation would work for me! But I am also interested in why my approach fails (which would be a lot more condensed and easier to read & understand) – mutableVoid Feb 27 '20 at 13:50

3 Answers3

5

Inspired by @dfri 's answer, I transformed her / his solution to a version which can omit functions, but instead uses only one struct using partial template specialization for the std::integer_sequence which might also be interesting to others:

template <auto arr, template <typename X, X...> typename Consumer,
          typename IS = decltype(std::make_index_sequence<arr.size()>())> struct Generator;

template <auto arr, template <typename X, X...> typename Consumer, std::size_t... I>
struct Generator<arr, Consumer, std::index_sequence<I...>> {
  using type = Consumer<typename decltype(arr)::value_type, arr[I]...>;
};

Full example with usage:

#include <array>

/// Structure which wants to consume the array via a parameter pack.
template <typename StructuralType, StructuralType... s> struct ConsumerStruct {
  constexpr auto operator()() const { return std::array{s...}; }
};

/// Solution
template <auto arr, template <typename X, X...> typename Consumer,
          typename IS = decltype(std::make_index_sequence<arr.size()>())> struct Generator;

template <auto arr, template <typename X, X...> typename Consumer, std::size_t... I>
struct Generator<arr, Consumer, std::index_sequence<I...>> {
  using type = Consumer<typename decltype(arr)::value_type, arr[I]...>;
};

/// Helper typename
template <auto arr, template <typename T, T...> typename Consumer>
using Generator_t = typename Generator<arr, Consumer>::type;

// Usage
int main() {
  constexpr auto tup = std::array<int, 3>{{1, 5, 42}};
  constexpr Generator_t<tup, ConsumerStruct> tt;
  static_assert(tt() == tup);
  return 0;
}
mutableVoid
  • 1,284
  • 2
  • 11
  • 29
  • I had a time to look over your approach, and it's very neat indeed, particularly the partial specialization technique. I've updated my own answer but to basically a minor variation of this one for C++17. I think you should accept your own answer instead of mine as this is now the initial source for this approach. – dfrib Feb 28 '20 at 14:39
  • I think your approach is more useful to most users because it also works with the c++17 standard & hints at my solution for those interested in this c++20-only solution. When I proposed to replace the lambda I didn't know that in C++17 this forces the non-type template to have static storage duration. Thank you very much for investing the time to consider my comment, I really appreciate that! :) Also my solution is basically a variation of your original answer, so that's fine :) – mutableVoid Feb 28 '20 at 16:35
  • 1
    Ok! Thanks to you too, I learned some new things from this instructive discussion :) – dfrib Feb 28 '20 at 16:47
2

A C++20 approach

See OP's own answer or, for possibly instructive but more verbose (and less useful) approach, revision 2 of this answer.

A C++17 approach

(This answer originally contained an approach using a minor C++20 feature (that a lambda without any captures may be default constructed), but inspired by the original answer the OP provided a much neater C++20 approach making use of the fact that a constexpr std::array falls under the kind of literal class that may be passed as a non-type template parameter in C++20 (given restraints on its ::value_type), combined with using partial specialization over the index sequence used to unpack the array into a parameter pack. This original answer, however, made use of a technique of wrapping std::array into a constexpr lambda (>=C++17) which acted as a constexpr (specific) std::array creator instead of an actual constexpr std::array. For details regarding this approach, see revision 2 of this answer)

Following OP's neat approach, below follows an adaption of it for C++17, using a non-type lvalue reference template parameter to provide, at compile time, a reference to the array to the array to struct target.

#include <array>
#include <cstdlib>
#include <tuple>
#include <type_traits>
#include <utility>

// Parameter pack structure (concrete target for generator below).
template <typename StructuralType, StructuralType... s>
struct ConsumerStruct
{
    // Use tuple equality testing for testing correctness.
    constexpr auto operator()() const { return std::tuple{s...}; }
};

// Generator: FROM std::array TO Consumer.
template <const auto& arr,
          template <typename T, T...> typename Consumer,
          typename Indices = std::make_index_sequence<arr.size()> >
struct Generator;

template <const auto& arr,
          template <typename T, T...> typename Consumer,
          std::size_t... I>
struct Generator<arr, Consumer, std::index_sequence<I...> >
{
    using type =
        Consumer<typename std::remove_cv<typename std::remove_reference<
                     decltype(arr)>::type>::type::value_type,
                 arr[I]...>;
};

// Helper.
template <const auto& arr, template <typename T, T...> typename Consumer>
using Generator_t = typename Generator<arr, Consumer>::type;

// Example usage.
int main()
{
    // As we want to use the address of the constexpr std::array at compile
    // time, it needs to have static storage duration.
    static constexpr std::array<int, 3> arr{{1, 5, 42}};
    constexpr Generator_t<arr, ConsumerStruct> cs;
    static_assert(cs() == std::tuple{1, 5, 42});
    return 0;
}

Note that this approach places a restriction on the std::array instance in that it needs to have static storage duration. If one wants to avoid this, using a constexpr lambda which generates the array may be used as an alternative.

Community
  • 1
  • 1
dfrib
  • 70,367
  • 12
  • 127
  • 192
  • Thank you :)! I like your answer a lot because it provides a very general way to solve my problem. Because it answers one of my questions (in fact the more important one) I accept this answer and ask a new question with a more minimal example on why exactly the definition does not match the usage of my struct. I think that the lambda is not needed in the code though (see my proposed edit). – mutableVoid Feb 27 '20 at 17:24
  • 1
    @mutableVoid thanks for the edit proposal, I didn't realize a constexpr `std::array` falls under the kind of literal class that may be passed as a non-type template parameter in C++20, nice! The edit was rejected by reviewers before I had a time to look at it, though, and I've seen you've since added you own answer, so I will update this answer later today and point to your answer for pure C++20 and see if I can make mine C++17 compatible instead. – dfrib Feb 28 '20 at 07:48
  • I see the OP is using gcc, but just a little note for future explorers that as of 2022 constexpr array handling in MSVC is broken, so the above code will not work. – stevecu Apr 28 '22 at 18:49
0

You can also do the following:

template<typename T, T... Values>
struct ResultStruct
{};

template<template<typename T, T... Values> typename ResultType, typename ElementType, size_t N, std::array<ElementType, N> Array, size_t... Is>
auto as_pack_helper(std::integer_sequence<size_t, Is...>) -> ResultType<ElementType, Array[Is]...>;

template<template<typename T, T... Values> typename ResultType, std::array Array>
using as_pack_t = decltype(as_pack_helper<ResultType, typename decltype(Array)::value_type, Array.size(), Array>(std::make_index_sequence<Array.size()>{}));

static_assert(std::is_same_v<as_pack_t<ResultStruct, std::array<int, 3>{1, 2, 3}>, ResultStruct<int, 1, 2, 3>>);

It works pretty similarly to the solution but uses decltype and a function definition to generate the result type!

Alex
  • 1,368
  • 10
  • 20