I am trying to model some meta data for serializing/de-serializing C++ objects. Here is something that captures the nuts & bolts of what I need; it compiles with GCC 5.2 (g++ sample.cpp -std=c++14
) and with Clang 3.6 (clang++ sample.cpp -std=c++14
).
My question is about the struct TypeInfo
in the example. It contains an std::initializer_list
of itself. Is this standards-conforming?
#include <cstdint>
#include <initializer_list>
enum class TypeCode : std::uint8_t { BOOLEAN, INT, OBJECT, STRING, SENTINEL };
struct TypeInfo
{
TypeCode typeCode_;
char fieldName_[64];
union
{
std::uint16_t textMinLength_;
std::uint16_t objectVersionMajor_;
};
union
{
std::uint16_t textMaxLength_;
std::uint16_t objectVersionMinor_;
};
// set only if typeCode_ = OBJECT
std::initializer_list < TypeInfo > objectTypeInfos_;
};
int main()
{
TypeInfo const sti { TypeCode::STRING, "updatedBy", { .textMinLength_ = 0 }, { .textMaxLength_ = 16 } };
TypeInfo const iti { TypeCode::INT, "amount", { 0 }, { 0 } };
TypeInfo const oti { TypeCode::OBJECT, "startTime", { .objectVersionMajor_ = 1 }, { .objectVersionMinor_ = 0 }, {
TypeInfo { TypeCode::INT, "weekdays", { 0 }, { 0 } },
TypeInfo { TypeCode::INT, "timeOfDay", { 0 }, { 0 } },
TypeInfo { TypeCode::STRING, "timezone", { .textMinLength_ = 0 }, { .textMaxLength_ = 5 } }
} };
TypeInfo const noti { TypeCode::OBJECT, "schedule", { .objectVersionMajor_ = 1 }, { .objectVersionMinor_ = 0 }, {
TypeInfo { TypeCode::INT, "id", { 0 }, { 0 } },
TypeInfo { TypeCode::STRING, "description", { .textMinLength_ = 0 }, { .textMaxLength_ = 16 } },
TypeInfo { TypeCode::OBJECT, "startTime", { .objectVersionMajor_ = 1 }, { .objectVersionMinor_ = 0 }, {
TypeInfo { TypeCode::INT, "weekdays", { 0 }, { 0 } },
TypeInfo { TypeCode::INT, "timeOfDay", { 0 }, { 0 } },
TypeInfo { TypeCode::STRING, "timezone", { .textMinLength_ = 0 }, { .textMaxLength_ = 5 } }
} }
} };
}
An object of type std::initializer_list<T> is a lightweight proxy object that provides access to an array of objects of type const T .
Initializer List is used in initializing the data members of a class. The list of members to be initialized is indicated with constructor as a comma-separated list followed by a colon. Following is an example that uses the initializer list to initialize x and y of Point class.
That actually induces undefined behavior with current wording. At the point of instantiation of std::initializer_list<TypeInfo>
, TypeInfo
is incomplete, hence [res.on.functions]/(2.5) applies:
In particular, the effects are undefined in the following cases:
(2.5) — if an incomplete type (3.9) is used as a template argument when instantiating a template component, unless specifically allowed for that component.
… and incomplete types are not specifically allowed for initializer_list
yet - however, that's clearly defective. LWG issue 2493 opts to fix this:
The typical use-case of
std::initializer_list<T>
is for a pass-by-value parameter ofT
's constructor. However, this contravenes [res.on.functions]/2.5 because initializer_list doesn't specifically allow incomplete types (as do for examplestd::unique_ptr
([unique.ptr]/5) andstd::enable_shared_from_this
([util.smartptr.enab]/2)).A resolution would be to copy-paste the relevant text from such a paragraph.
I.e. your code is fine (and will be officially fine after resolution of the aforementioned DR).
§ [res.on.functions]/2:
In particular, the effects are undefined in the following cases:
[...]
(2.5) - if an incomplete type (3.9) is used as a template argument when instantiating a template component, unless specifically allowed for that component.
I see no such specific allowance for initializer_list
to be instantiated over an incomplete type (in either §[dcl.init.list] or §[support.init.list], at least as of N4296).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With