0

Say I have struct S:

struct S {
const int i;
const bool b;
}

And I want to create an (non-const) array of this struct like so:

std::array<S, 16> arr;
for(int i = 0; i<arr.size(); i++)
    arr[i] = { i, i%2==0 };

Then the compiler will complain that I didn't initialize the a const variable when I initialized the array.

I tried doing it with a vector as an intermediary. But int the function I'm writing I have to pass the original array in another struct, and return that other struct.

struct OtherStruct {
    const std::array<S,16> sArray;
};

OtherStruct f() {
    std::vector<S> vec(16);
    for(int i = 0; i<16; i++)
        vec.push_back({ i, i%2==0 });
    return { vec.data() };
}

But that didn't work either. I hoped that passing the pointer to the vector data would be cast into a C-style array, from which an std::array could be made. What is the cleanest way to fix this?

I'm working with C++11.

Note that this example is a gross simplification. The line arr[i] = { i, i%2==0 }; would be replaced by a function that parses incoming network data. The actual array is way bigger and the struct is also more complicated. None of the data that will populate the struct will be known at compile time, only the layout.

wovano
  • 4,543
  • 5
  • 22
  • 49
Typhaon
  • 828
  • 8
  • 27
  • Please read about contructor initialization list. You can also define helper function which will be used to initialize field: `std::array createAlternatingOnesZeros()`. – Marek R Nov 02 '22 at 09:23
  • @MarekR Yes I have looked into that. But how would I create that initialization_list from my vec? The actual code that I have to instantiate "struct S" is actually done by parsing some raw binary data, so It's a bit more complicated than a single line. The array Is also actually of size 800. So I would really like to have a for loop in here somewhere. – Typhaon Nov 02 '22 at 09:30
  • Not that. This thing: https://en.cppreference.com/w/cpp/language/constructor – Marek R Nov 02 '22 at 09:34
  • @MarekR Ah, yes, but a function is not possible, because it has to return an std::array, which I'm not able to instantiate. And a braced initializer list is also not possible because the size of the array is 800, and the actual struct that I'm passing into it has quite a bit more fields than 2. – Typhaon Nov 02 '22 at 09:39
  • 1
    @Typhaon If you need an initializer list like `return {vec[0], vec[1], ...`, you can create that with `BOOST_PP_REPEAT`: https://www.boost.org/doc/libs/1_77_0/libs/preprocessor/doc/ref/repeat.html – VLL Nov 02 '22 at 09:45
  • Re. `"braced initializer list is also not possible because the size of the array is 800"`: why does that make it impossible? If the size of the array and the values of all data members are known at compile-time then it should be possible to construct a suitable initializer list. – G.M. Nov 02 '22 at 09:45
  • @G.M. Because to my knowledge you can't loop in an braced initializer list. If I have to write down `{ createS(input, offset), createS(input, offset+1), createS(input, offset+2), ... }` Then I don't think that will produce very clean code. – Typhaon Nov 02 '22 at 09:53
  • @VLL Ah nice! I'll look into that! – Typhaon Nov 02 '22 at 09:53
  • 2
    This sounds like an [XY problem](https://en.wikipedia.org/wiki/XY_problem): `const` data members is typically an anti-pattern and e.g. the [C++ Core Guidelines have a rule against it](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-constref). It prohibit moves and adds complexity to clients of the type, such as highlighted above. It seems what you are after is _logical constness_, which can be implemented using a class type instead, with read-only access to its non-const data members. – dfrib Nov 02 '22 at 10:02
  • 1
    You still can [use placement-new to fill the array items](https://godbolt.org/z/9qzPqPWhc), but I would not recommend it. Usually, it's better to make the members `private` instead of `const` (or work with a `const S` instead). – Fareanor Nov 02 '22 at 10:10
  • @dfrib I... I did not know that. I come from Kotlin and I was using Rust before this internship, so I leaned heavily on immutability for safety. I think I will need to take a day to change all my structs. Thanks for letting me know. – Typhaon Nov 02 '22 at 10:11
  • @Fareanor After dfrib's comment I think I will be returning to the style of private/public and getters/setters Thanks for your comment :) – Typhaon Nov 02 '22 at 10:18
  • You're right, this is the way to go :) – Fareanor Nov 02 '22 at 10:19

3 Answers3

2

You can use parameter pack expansion to create an initialiser list of arbitrary size.

You will need to backport std::index_sequence into C++11

template <size_t... Is>
std::array<S, sizeof...(Is)> make_s_array_impl(index_sequence<Is...>) { 
    return { { { Is, Is % 2 == 0 }... } }; 
}

template <size_t N>
std::array<S, N> make_s_array()
{
    return make_s_array_impl(make_index_sequence<N>{});
}

See it live

Caleth
  • 52,200
  • 2
  • 44
  • 75
1

As the number of values is known in the compile time, you can fill the array with an initializer list. You can create it easily with BOOST_PP_REPEAT:

#include <boost/preprocessor/repetition/repeat.hpp>

struct S {
    const int i;
    const bool b;
};

struct OtherStruct {
    const std::array<S,16> sArray;
};

OtherStruct f() {
    #define FILL(z, i, ignored) { i, i%2==0 },

    return {std::array<S,16>{{
        BOOST_PP_REPEAT(16, FILL, ignored)
    }}};

    #undef FILL
}
VLL
  • 9,634
  • 1
  • 29
  • 54
-1

In C/C++ when you use the const keyword you cannot left the variable uninitialized when you declare it and neither assign a new value after this is declared.

For example:

const int i = 5;  //this is a correct way to use the const keyword
const int i;
...
i = 5;  //this is NOT  a correct way to use the const keyword

However, you can use const_cast<T> to bind a pointer of the same type to the value of your struct instance and change it only once in the for loop.

In this way you can obtain what you asked.

#include <iostream>
#include <array>


struct S{ 
const bool b = 0; //Intialize the values in order to avoid compiler errors.
const int i = 0;
};
int main(){
    std::array<struct S, 16> arr;
    for(int i = 0; i<arr.size(); i++){
        // declare a variable that point to the const value of your struct instance.
        bool* b = const_cast <bool*> (&arr[i].b);
        int* in  = const_cast <int*> (&arr[i].i);
        *b = i; //change the value with the value you need.
        *in = i%2==0;
            
        }
        
        for(int i = 0; i<arr.size(); i++){

    int a = i%2==0;
    std::cout<< "const bool: " << arr[i].b << " " << (bool)i  << "const int: " << arr[i].i << " " << a << std::endl;
            
        }
    
    
}
Gio
  • 61
  • 1
  • 6
  • This won't work because the compiler will complain at `std::array arr;` Because that's the moment it initializes everything, and const variables don't have a default initialization – Typhaon Nov 02 '22 at 10:22
  • More importantly, using `const_cast` to modify a `const` object is a constness violation and undefined behaviour. This is NOT the intended use of `const_cast`. Never ever do this. – Fareanor Nov 02 '22 at 10:24