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 ofphase_twowhere it becomes a dangling pointer.Your requirements seem to be that
ReadableIdsdoesn'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_listconstruction can extend the lifetime to that of thestd::initializer_listobject 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 temporarystd::initializer_listobject.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:
Now you may use the syntax
and the array referenced by
_idswill live as long asphase_one, but it won't be copied or have its lifetime extended ifphase_oneis copied/moved! A copy will refer to the same array andstd::initializer_listobject! Copying/movingstd::initializer_listexplicitly also wont copy/move or extend the lifetime of the underlying array!In particular, if
phase_onewasn'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 whichphase_oneis declared.The lifetime extension also doesn't work if you use parentheses instead of braces for initialization of
phase_oneor 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:The requirements on the initialization still apply, but there is less potential on mistakenly copying
_ids.