I have a number of static instances of a class of data which hold onto arrays of integers, simplied in the following:
class ReadableIds
{
public:
const int * ids;
ReadableIds( const int * _ids ) : ids(_ids) { }
// ... a bunch of methods that operate on ids, not germane to the discussion
};
I currently populate them in the following way at filescope:
foo.cpp:
//
static const int phase_one_ids[] = { 1, 10, 3, 2, -1 };
static ReadableIds phase_one( phase_one_ids );
static const int phase_two_ids[] = { 31, 11, 23, 542, 11, 88, -1 };
static ReadableIds phase_two( phase_two_ids );
However, I really want them to be defined inline for legibility reasons ( the class is more complicated with other arguments passed to the constructor)
static ReadableIds phase_one( { 1, 10, 3, 2, -1 }, ... other args );
static ReadableIds phase_two( { 31, 11, 23, 542, 11, 88, -1 }, ... other args );
The only way I can compile this is if I introduce a std::initializer_list:
class ReadableIds
{
public:
ReadableIds(const std::initializer_list<int> & _ids ) :
ids(_ids.begin()) // take address of initializer_list !!
{
}
};
But my understanding is that the std::initializer_list is temporary, so holding the address to that data is wrong.
Question: Without allocating memory dynamically, is there a solution to this to allow better syntax? Doing a copy results in the static memory consumption, and the copy of the data, and in my use case that isn't viable.
No, you can't store the result of _ids.begin()
, or rather: You can store it, but can't dereference it after the initialization of phase_two
where it becomes a dangling pointer.
Your requirements seem to be that
ReadableIds
doesn't actually store the elements itself.Then the only way is to store the elements in a temporary array object. The problem is that temporaries are normally destroyed after full-expression in which they are manifested, which is not what you want.
std::initializer_list
construction can extend the lifetime to that of the std::initializer_list
object itself, but if you don't want to name a variable you now didn't do anything but shift the problem to extending the lifetime of the temporary std::initializer_list
object.
The only way to extend the lifetime of a temporary object to that of a class object is to store a reference (in)to the temporary object directly in the class and initializing it immediately with the newly manifested temporary via braced aggregate initialization.
So, you can do:
// DON'T USE THIS!
class ReadableIds
{
public:
// must be aggregate, no constructors!
std::initializer_list<int>&& _ids; // must be reference!
// other aggregate elements
};
Now you may use the syntax
// DON'T USE THIS!
// must be braces!
static ReadableIds phase_one{ { 1, 10, 3, 2, -1 },
/*initializers for other aggregate elements*/ };
and the array referenced by _ids
will live as long as phase_one
, but it won't be copied or have its lifetime extended if phase_one
is copied/moved! A copy will refer to the same array and std::initializer_list
object! Copying/moving std::initializer_list
explicitly also wont copy/move or extend the lifetime of the underlying array!
In particular, if phase_one
wasn't a static storage duration object, then the array will not live until the end of the program, only ever until the end of the scope in which phase_one
is declared.
The lifetime extension also doesn't work if you use parentheses instead of braces for initialization of phase_one
or if you insert a constructor.
All in all I would not recommend this. It is very fragile and not worth it to avoid naming one extra variable. (I would also not be sure that compilers correctly implement this special case of nested lifetime extension.)
Also, reconsider whether the first requirement really is necessary. It is easy enough to make the class a template that automatically deduces the correct size for an internal array.
A slightly safer variation would be to use an array instead of std::initializer_list
. In that case you need to template the class on the size of the array though:
// DON'T USE THIS!
template<std::size_t N>
class ReadableIds
{
public:
// must be aggregate, no constructors!
int (&&_ids)[N]; // must be reference!
// other aggregate elements
};
The requirements on the initialization still apply, but there is less potential on mistakenly copying _ids
.