Reputation: 739
I'm currently doing c++ with OpenCL, where a c-style struct is required to carry configuration information from the c++ host to the OpenCL kernel. Given that dynamically allocated arrays are not guaranteed to be supported by every OpenCL implementation, I must ensure every array accessible by the kernel code be static-sized. However, I run into weird errors when initializing static arrays within a c-style struct.
The error could be reproduced by the following PoC:
#include <cstring>
#include <string>
#define ID_SIZE 16
struct conf_t {
const unsigned int a;
const unsigned int b;
const unsigned char id[ID_SIZE];
};
int main() {
const std::string raw_id("0123456789ABCDEF");
unsigned char id[ID_SIZE];
memcpy(id,raw_id.c_str(),ID_SIZE);
struct conf_t conf = {10,2048,id};
}
And the following error:
poc.cc: In function ‘int main()’:
poc.cc:15:39: error: array must be initialized with a brace-enclosed initializer
15 | struct conf_t conf = {10,2048,id};
| ^~
It's true that I could remove the const keyword in the struct and get rid of the stack variable id
, where &(conf.id)
could be the first parameter of memcpy
. However, I'd like to keep the immutability of fields in the conf struct, which enables the compilers to check undesired modifications.
For my understanding, structs in c should have the following memory layout:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| a |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| b |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
+ id +
| |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Given the stack variable id
is also with static size, I'm confused why the c++ compiler still looks for a brace-enclosed initializer even if id
is already a static-sized array.
Upvotes: 2
Views: 692
Reputation: 3973
If you want to copy the entire string, you have to use memcopy
into conf.id
(or strncpy
if it is guaranteed to be a zero-terminated string). Unfortunately this means that the id
in conf_t
cannot be const
anymore:
#include <iostream>
#include <cstring>
#include <string>
#define ID_SIZE 16
struct conf_t
{
const unsigned int a;
const unsigned int b;
unsigned char id[ID_SIZE];
};
int main()
{
const std::string raw_id("0123456789ABCDE");
conf_t conf = {10, 2048, {0}};
memcpy(conf.id, raw_id.c_str(), ID_SIZE); // <- memcopy from the string into conf.id
std::cout << conf.id << '\n';
std::cout << std::boolalpha;
std::cout << std::is_pod<conf_t>::value << '\n';
}
On the other hand, if conf_t.id
must be const
, then I believe you must use a compile-time constant initialization in order to keep conf_t
a POD class:
struct conf_t
{
const unsigned int a;
const unsigned int b;
const unsigned char id[ID_SIZE];
};
int main()
{
conf_t conf = {10, 2048, "0123456789ABCDE"};
...
It is also possible to use a template constructor to turn a dynamic array into an initializer-list. This will enable you to initialize a const
c-array with dynamic data, but it adds a constructor to conf_t
which means that it no longer is a POD class.
#include <iostream>
#include <cstring>
#include <string>
#include <utility>
#define ID_SIZE 16
struct conf_t
{
const unsigned int a;
const unsigned int b;
const unsigned char id[ID_SIZE];
conf_t(const unsigned int a,
const unsigned int b,
const unsigned char (&arr)[ID_SIZE])
: conf_t(a, b, arr, std::make_index_sequence<ID_SIZE>())
{
}
private:
template <std::size_t... Is>
conf_t(const unsigned int a,
const unsigned int b,
const unsigned char (&arr)[ID_SIZE], std::index_sequence<Is...>)
: a{a},
b{b},
id{arr[Is]...}
{
}
};
int main()
{
const std::string raw_id("0123456789ABCDE");
unsigned char id[ID_SIZE];
memcpy(id, raw_id.c_str(), ID_SIZE);
conf_t conf = {10, 2048, id};
std::cout << conf.a << '\n';
std::cout << conf.b << '\n';
std::cout << conf.id << '\n';
std::cout << std::boolalpha;
std::cout << std::is_pod<conf_t>::value << '\n';
}
It is possible that I have missed something though, so I welcome any corrections.
Upvotes: 1
Reputation: 6471
Something is odd in your declaration. I suspect most of you issues come from an abusive usage of const.
Your conf_t definition reads:
struct conf_t {
const unsigned int a;
const unsigned int b;
const unsigned char id[ID_SIZE];
};
Why are you declaring constant members? And why are they uninitialized? The fact that they are all declared const and none of them are initialized makes me suspect a big code smell. If you require a constant conf_t, then declare it the usual way, as in:
struct conf_t {
unsigned int a; // note that the const keyword has disappeared.
unsigned int b;
unsigned char id[ID_SIZE];
};
// the conf_t variable is declared const..
const conf_t my_conf { 10, 2048, "0123456789ABCDE" };
// et voilà !
If you need more flexibility, (ie: initialize id from an lvalue), you can either define a constructor as suggested by @Frodyne, or create an initializer function, this solution has the advantage of keeping the POD property of you simple conf_t struct.
Example:
struct conf_t {
unsigned int a;
unsigned int b;
unsigned char id[ID_SIZE]; // is there a VERY good reason why this is unsigned char?
};
// initializes a conf_t, I guess you cpould call that a
// pseudo-constructor of sorts.
conf_t make_conf(int a, int b, const char* id)
{
// our contract:
assert(id != NULL && strlen(id) < ID_SIZE); // or strlen(id) == ID_SIZE - 1, if you wish
conf_t result = { a, b };
strncpy((char*)result.id, id, sizeof(result.id));
result.id[sizeof(result.id) - 1] = 0;
// all c++ compilers less than 20 years old will return in-place
return result;
}
// declaration from literals then becomes:
const conf_t cfg1 = make_conf(1, 2, "0123456789ABCDE");
// declaration from l-values becomes:
int x = 123;
int y = 42;
std::string s = "EDCBA9876543210";
const conf_t cfg2 = make_conf(x, y, s.c_str());
Which shouldn't be too taxing on the fingers, nor on the eyes.
Upvotes: 0
Reputation: 2209
The error on the initialization is that you cannot initialize an array element of type const unsigned char
with an lvalue of type unsigned char[16]
.
What you need to initialize your member, is to use the correct initialization syntax with curly braces { vals... }
, and use the correct types on the initialization values. But, this isn't so easy in standard C++, because you are triying to initialize a const
value from a non-const
one.
One workaround without losing const
in your id
member is to simply change it to const unsigned char*
. Here the pointer type is the key.
Then, you can use some obscure monsters that exists in C++
, like reinterpret_cast<T>
, and initialize your const member from a non const one.
#include <cstring>
#include <string>
#define ID_SIZE 16
struct conf_t {
const unsigned int a;
const unsigned int b;
const unsigned char* id[ID_SIZE];
};
int main() {
const std::string raw_id("0123456789ABCDEF");
unsigned char id[ID_SIZE];
memcpy(id, raw_id.c_str(), ID_SIZE);
struct conf_t conf = {10, 2048, reinterpret_cast<const unsigned char*>(id)};
std::cout << *(conf.id);
}
And everything compiles fine now. You can see a live example here
Upvotes: 0
Reputation: 6471
Try either of these syntaxes:
struct conf_t {
const unsigned int a;
const unsigned int b;
const unsigned char id[ID_SIZE];
};
conf_t syntax_1 = { 10, 1, { 'a', 'b', 'c' }}; // an array needs its own {}
conf_t syntax_2 = { 10, 1, "hello" }; // an array of char can be a string.
// make sure you have room for the
// null termination!
Upvotes: 1