1

For example:

struct thing1 {
    int very_important_integer = 5;
};

struct thing2 {
    thing1 &thing;
    thing2(thing1 &A) : thing(A) {}
};

int main() {
    // in practice, this array is 49,500 elements large, not just 5
    thing1 stuff[5]; // OK
    thing2 arr[5];   // error: no default constructor for thing2
}

I can't add a default constructor to thing2 because there is a reference to be initialized.

9
  • 2
    To which objects must the references of the array elements bind? Commented Oct 12, 2023 at 1:11
  • An arr of thing1s Commented Oct 12, 2023 at 1:12
  • Can you use an initializer list? thing2 arr[5]{ stuff[0], stuff[1], stuff[2], stuff[3], stuff[4] }; Commented Oct 12, 2023 at 1:20
  • I could, but I'm using 45,900 of these. (Don't ask why) ;D Commented Oct 12, 2023 at 1:28
  • 1
    change the reference to pointer and set it in a while/for loop? Commented Oct 12, 2023 at 1:33

3 Answers 3

3

Not sure it is what you want, but it's the only option - init elements at the creation.

struct thing1 {
  int veryimportantinteger = 5;
};

struct thing2 {
  thing1& thing;
  thing2(thing1& A) : thing(A) {}
};

int main() {
  thing1 stuff[5];
  thing2 arr[5]{stuff[0], stuff[1], stuff[2], stuff[3], stuff[4]};
}

The example with pointers

struct thing2 {
  thing1* thing{};
  thing2() = default;
  thing2(thing1& A) : thing(&A) {}
};

int main() {
  thing1 stuff[5];
  thing2 arr[5]{};
  for (size_t i = 0; i < std::min(std::size(arr), std::size(stuff)); ++i)
    arr[i] = stuff[i];
}
Sign up to request clarification or add additional context in comments.

4 Comments

I hope there might be a better way, and I would accept it, but as mentioned, I hope there is a better way, and perhaps somebody knows it(Most ppl don't answer with accepted ones around). (No offense!)
Use pointers, that can be zero or default initialized.
and I'm using 45,900 of these. I could have python type it out while I get lunch tho. XD
oh ok @273K, could you give an ex?
0

If you change c-array by std::array, you might create a function to create the array:

// const variant omitted.
// C++20
template <typename T, std::size_t N, typename From>
std::array<T, N> make_array_from(std::array<From, N>& froms) {
    return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
        return std::array<T, N>{{T{froms[Is]}...}};
    }(std::make_index_sequence<N>());
}

// C++17
template <typename T, std::size_t N, typename From, std::size_t... Is>
std::array<T, N> make_array_from_impl(std::index_sequence<Is...>, std::array<From, N>& froms) {
    return std::array<T, N>{{T{froms[Is]}...}};
}

template <typename T, std::size_t N, typename From>
std::array<T, N> make_array_from(std::array<From, N>& froms) {
    return make_array_from_impl(std::make_index_sequence<N>(), froms);
}

With usage

std::array<thing1, 5> stuff{/*..*/};
auto arr = make_array_from<thing2>(stuff);

Demo

4 Comments

Note that this is going to have horrendous codegen and compilation speed for 49,500 elements, if it compiles at all (might exceed implementation-defined limits). I'm not sure if there's any way to solve that though.
@JanSchultke: compiler might have "builtin" to have index_sequence in 1 instantiation. So, I don't expected issue in that case (no more than harcoding the array manually).
The problem comes from the fact that you create an initializer list {T{froms[Is]}...} with 49,500 elements. This has catastrophic impact on compile times. The issue of creating an index_sequence in the first place is trivial and has been solved by modern compilers, yes. Also, that list initialization does not get merged into a loop; you get the same codegen as a fully unrolled loop with 49,500 iterations, which may be a massive increase in code size. That is, if implementation-defined limits are not exceeded through such a massive list.
@JanSchultke: "no more than harcoding the array manually". So, that is the problem, ok.
0

You can use placement-new to explicitly construct each element of arr[] yourself, thus allowing you to call the thing2 constructor on each element, such as in a loop.

Simply pre-allocate a buffer of sufficient size and alignment to hold the objects, then placement-construct each object at the appropriate offset within the buffer, then you can use the objects normally as needed, and then finally destruct the objects and free the buffer when you are done using it.

For example:

struct thing1{
  int veryimportantinteger = 5;
};

struct thing2{
  thing1 &thing;
  thing2(thing1 &A):thing(A){}
};

int main(){
  thing1 stuff[5];
  char buffer[sizeof(thing2)*5];
  thing2 *arr = reinterpret_cast<thing2*>(buffer);
  for (int i = 0; i < 5; ++i){
    new (&arr[i]) thing2(stuff[i]);
  }
  //use arr as needed...
  for (int i = 0; i < 5; ++i){
    arr[i].~thing2();
  }
}

In modern C++, you should use things like std::aligned_storage/alignas, std::launder, std::construct_at()/std::destroy(), etc to make this code safer/correct, but the underlying principle still stands.

11 Comments

aligned_storage is deprecated.
In any case, I don't think this is a good solution. When exceptions become involved, one would need to be very careful to make this exception-safe.
@HolyBlackCat I know it's deprecated, in C++23, but I dont now what its replacement is
alignas(T) char array[sizeof(T)] is the replacement.
@HolyBlackCat Thanks, apparently I've seen that kind of example before. That example is allocating an array which can hold only 1 element, but in this case we would need an array of such elements
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.