Django
Django

Reputation: 57

Is there an extensible way to initialize a struct from an array in C++?

struct SomeStruct {
    int a;
    int b;
    int c;
    int d;
};

void main() {
    std::array<int, 4> arr {1, 2, 3, 4};
    SomeStruct someStruct { ??? }; // <------ Initialize with arr? (Ideal would be {arr})
}

As seen in the code example, is there some shortcut to initialize someStruct with arr? Instead of resorting to:

    SomeStruct someStruct {arr[0], arr[1], arr[2], arr[3]};

Upvotes: 3

Views: 635

Answers (3)

walnut
walnut

Reputation: 22152

You can use std::apply to create a parameter pack and use that in the initializer:

auto someStruct = std::apply([](auto&&... args){return SomeStruct{decltype(args)(args)...};}, arr);

You can put this in a function to use it comfortably:

template<typename T, typename A>
constexpr T make_from_tuple_list_init(A&& a) {
    return std::apply([](auto&&... args){return T{decltype(args)(args)...};}, std::forward<A>(a));
}

Then use it as

auto someStruct = make_from_tuple_list_init<SomeStruct>(arr);

This requires C++17 and #include<tuple>, but can be implemented in earlier C++ versions (with some more effort) as well. Also in C++17 this is guaranteed to use copy elision, so there will be only one SomeStruct constructed. Before C++17 some move constructions might be involved (although compilers were already permitted to and did implement return value optimization with the same effect).

std::apply also has the benefit that it works with every type that implements std::get and std::tuple_size, meaning that it will work for example with std::array, std::tuple, std::pair and other user-defined types with that interface.

One might want to make two versions of this function, one that uses T{...} as above and one that uses T(...), so that one is able to choose between list-initialization and direct-initialization.

However, the T(...) version has been implemented in the standard library since C++17 as std::make_from_tuple. In C++20, aggregate initialization will also work with parentheses, so std::make_from_tuple will work for your case then directly.


For a C++14 implementation, take this example from cppreference's page on std::make_from_tuple:

namespace detail {
template <class T, class Tuple, std::size_t... I>
constexpr T make_from_tuple_impl( Tuple&& t, std::index_sequence<I...> )
{
  return T(std::get<I>(std::forward<Tuple>(t))...);
}
} // namespace detail

template <class T, class Tuple>
constexpr T make_from_tuple( Tuple&& t )
{
   return detail::make_from_tuple_impl<T>(std::forward<Tuple>(t),
       std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Tuple>>>{});
}

and replace std::tuple_size_v<...> with std::tuple_size<...>::value.

This is for the T(...) version. You would need to replace T(...) with T{...} to get the T{...} version I implemented above.

Upvotes: 11

Rahim
Rahim

Reputation: 1

You can use one of the Memory Transfer functions. {memcpy, CopyMemory}

this is an example:

#include <iostream>
#include <array>
#include <string.h>

using namespace std;

int main()
{
   array<int,4> ar = {1,2,3,4};

   struct myStruct
   {
      int a;
      int b;
      int c;
      int d;
   };

 myStruct stc;

memcpy(&stc, ar.data(), sizeof(stc));


cout << "My struct elements are: " << stc.a << " - " << stc.b << " - "<< stc.c << " - " << stc.d << endl;
return 0; 
}

Upvotes: -2

YSC
YSC

Reputation: 40080

You can define a constructor taking a pair of iterators. Sometimes simplicity wins over flexibility.

#include <array>

struct SomeStruct {
    int a;
    int b;
    int c;
    int d;

    template<class InputIt>
    SomeStruct(InputIt begin, InputIt end)
    {
        if (begin != end) a = *begin++;
        if (begin != end) b = *begin++;
        if (begin != end) c = *begin++;
        if (begin != end) d = *begin++;
    }
};

Demo: https://coliru.stacked-crooked.com/a/7e235a8022b048ac

Upvotes: 1

Related Questions