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

后端 未结 2 665
隐瞒了意图╮
隐瞒了意图╮ 2021-01-14 09:37

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 inte

相关标签:
2条回答
  • 2021-01-14 09:47

    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;
    }
    
    0 讨论(0)
  • 2021-01-14 10:09

    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.

    0 讨论(0)
提交回复
热议问题