How do I “expand” a compile-time std::array into a parameter pack?

只愿长相守 提交于 2020-03-22 08:08:44

问题


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).


回答1:


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.




回答2:


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;
}


来源:https://stackoverflow.com/questions/60434033/how-do-i-expand-a-compile-time-stdarray-into-a-parameter-pack

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!