How to emulate C array initialization “int arr[] = { e1, e2, e3, … }” behaviour with std::array?

后端 未结 10 769
难免孤独
难免孤独 2020-11-22 17:23

(Note: This question is about not having to specify the number of elements and still allow nested types to be directly initialized.)
This question discu

相关标签:
10条回答
  • 2020-11-22 17:49

    (Solution by @dyp)

    Note: requires C++14 (std::index_sequence). Although one could implement std::index_sequence in C++11.

    #include <iostream>
    
    // ---
    
    #include <array>
    #include <utility>
    
    template <typename T>
    using c_array = T[];
    
    template<typename T, size_t N, size_t... Indices>
    constexpr auto make_array(T (&&src)[N], std::index_sequence<Indices...>) {
        return std::array<T, N>{{ std::move(src[Indices])... }};
    }
    
    template<typename T, size_t N>
    constexpr auto make_array(T (&&src)[N]) {
        return make_array(std::move(src), std::make_index_sequence<N>{});
    }
    
    // ---
    
    struct Point { int x, y; };
    
    std::ostream& operator<< (std::ostream& os, const Point& p) {
        return os << "(" << p.x << "," << p.y << ")";
    }
    
    int main() {
        auto xs = make_array(c_array<Point>{{1,2}, {3,4}, {5,6}, {7,8}});
    
        for (auto&& x : xs) {
            std::cout << x << std::endl;
        }
    
        return 0;
    }
    
    0 讨论(0)
  • 2020-11-22 17:50

    Best I can think of is:

    template<class T, class... Tail>
    auto make_array(T head, Tail... tail) -> std::array<T, 1 + sizeof...(Tail)>
    {
         std::array<T, 1 + sizeof...(Tail)> a = { head, tail ... };
         return a;
    }
    
    auto a = make_array(1, 2, 3);
    

    However, this requires the compiler to do NRVO, and then also skip the copy of returned value (which is also legal but not required). In practice, I would expect any C++ compiler to be able to optimize that such that it's as fast as direct initialization.

    0 讨论(0)
  • 2020-11-22 17:50

    I'd expect a simple make_array.

    template<typename ret, typename... T> std::array<ret, sizeof...(T)> make_array(T&&... refs) {
        // return std::array<ret, sizeof...(T)>{ { std::forward<T>(refs)... } };
        return { std::forward<T>(refs)... };
    }
    
    0 讨论(0)
  • 2020-11-22 17:57

    Using trailing return syntax make_array can be further simplified

    #include <array>
    #include <type_traits>
    #include <utility>
    
    template <typename... T>
    auto make_array(T&&... t)
      -> std::array<std::common_type_t<T...>, sizeof...(t)>
    {
      return {std::forward<T>(t)...};
    }
    
    int main()
    {
      auto arr = make_array(1, 2, 3, 4, 5);
      return 0;
    }
    

    Unfortunatelly for aggregate classes it requires explicit type specification

    /*
    struct Foo
    {
      int a, b;
    }; */
    
    auto arr = make_array(Foo{1, 2}, Foo{3, 4}, Foo{5, 6});
    

    In fact this make_array implementation is listed in sizeof... operator


    c++17 version

    Thanks to template argument deduction for class templates proposal we can use deduction guides to get rid of make_array helper

    #include <array>
    
    namespace std
    {
    template <typename... T> array(T... t)
      -> array<std::common_type_t<T...>, sizeof...(t)>;
    }
    
    int main()
    {
      std::array a{1, 2, 3, 4};
      return 0; 
    }
    

    Compiled with -std=c++1z flag under x86-64 gcc 7.0

    0 讨论(0)
  • 2020-11-22 17:58

    Create an array maker type.

    It overloads operator, to generate an expression template chaining each element to the previous via references.

    Add a finish free function that takes the array maker and generates an array directly from the chain of references.

    The syntax should look something like this:

    auto arr = finish( make_array<T>->* 1,2,3,4,5 );
    

    It does not permit {} based construction, as only operator= does. If you are willing to use = we can get it to work:

    auto arr = finish( make_array<T>= {1}={2}={3}={4}={5} );
    

    or

    auto arr = finish( make_array<T>[{1}][{2}[]{3}][{4}][{5}] );
    

    None of these look like good solutions.

    Using variardics limits you to your compiler-imposed limit on number of varargs and blocks recursive use of {} for substructures.

    In the end, there really isn't a good solution.

    What I do is I write my code so it consumes both T[] and std::array data agnostically -- it doesn't care which I feed it. Sometimes this means my forwarding code has to carefully turn [] arrays into std::arrays transparently.

    0 讨论(0)
  • 2020-11-22 18:03

    Combining a few ideas from previous posts, here's a solution that works even for nested constructions (tested in GCC4.6):

    template <typename T, typename ...Args>
    std::array<T, sizeof...(Args) + 1> make_array(T && t, Args &&... args)
    {
      static_assert(all_same<T, Args...>::value, "make_array() requires all arguments to be of the same type."); // edited in
      return std::array<T, sizeof...(Args) + 1>{ std::forward<T>(t), std::forward<Args>(args)...};
    }
    

    Strangely, can cannot make the return value an rvalue reference, that would not work for nested constructions. Anyway, here's a test:

    auto q = make_array(make_array(make_array(std::string("Cat1"), std::string("Dog1")), make_array(std::string("Mouse1"), std::string("Rat1"))),
                        make_array(make_array(std::string("Cat2"), std::string("Dog2")), make_array(std::string("Mouse2"), std::string("Rat2"))),
                        make_array(make_array(std::string("Cat3"), std::string("Dog3")), make_array(std::string("Mouse3"), std::string("Rat3"))),
                        make_array(make_array(std::string("Cat4"), std::string("Dog4")), make_array(std::string("Mouse4"), std::string("Rat4")))
                        );
    
    std::cout << q << std::endl;
    // produces: [[[Cat1, Dog1], [Mouse1, Rat1]], [[Cat2, Dog2], [Mouse2, Rat2]], [[Cat3, Dog3], [Mouse3, Rat3]], [[Cat4, Dog4], [Mouse4, Rat4]]]
    

    (For the last output I'm using my pretty-printer.)


    Actually, let us improve the type safety of this construction. We definitely need all types to be the same. One way is to add a static assertion, which I've edited in above. The other way is to only enable make_array when the types are the same, like so:

    template <typename T, typename ...Args>
    typename std::enable_if<all_same<T, Args...>::value, std::array<T, sizeof...(Args) + 1>>::type
    make_array(T && t, Args &&... args)
    {
      return std::array<T, sizeof...(Args) + 1> { std::forward<T>(t), std::forward<Args>(args)...};
    }
    

    Either way, you will need the variadic all_same<Args...> type trait. Here it is, generalizing from std::is_same<S, T> (note that decaying is important to allow mixing of T, T&, T const & etc.):

    template <typename ...Args> struct all_same { static const bool value = false; };
    template <typename S, typename T, typename ...Args> struct all_same<S, T, Args...>
    {
      static const bool value = std::is_same<typename std::decay<S>::type, typename std::decay<T>::type>::value && all_same<T, Args...>::value;
    };
    template <typename S, typename T> struct all_same<S, T>
    {
      static const bool value = std::is_same<typename std::decay<S>::type, typename std::decay<T>::type>::value;
    };
    template <typename T> struct all_same<T> { static const bool value = true; };
    

    Note that make_array() returns by copy-of-temporary, which the compiler (with sufficient optimisation flags!) is allowed to treat as an rvalue or otherwise optimize away, and std::array is an aggregate type, so the compiler is free to pick the best possible construction method.

    Finally, note that you cannot avoid copy/move construction when make_array sets up the initializer. So std::array<Foo,2> x{Foo(1), Foo(2)}; has no copy/move, but auto x = make_array(Foo(1), Foo(2)); has two copy/moves as the arguments are forwarded to make_array. I don't think you can improve on that, because you can't pass a variadic initializer list lexically to the helper and deduce type and size -- if the preprocessor had a sizeof... function for variadic arguments, perhaps that could be done, but not within the core language.

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