I've got a class that basically contains a std::vector.
I can default-construct an object of this class, leaving the contained std::vector empty.
I can also value-construct it from a set of values, given through a braced-initializers list.
I'd like to have a consistent behavior between both forms,per se, passing an empty list should give an empty std::vector.
Yet I cannot reproduce the expected behavior, as the empty list actually construct a single default element list:
#include <iostream>
#include <vector>
struct Wrap {
std::vector<double> v;
Wrap() = default;
Wrap(std::initializer_list<double> const &l) : v(l) {}
};
int main() {
Wrap v0 = {};
std::cout << v0.v.size() << "; expected 0\n";
Wrap v1 = {{}};
// expecting 0, getting 1
std::cout << v1.v.size() << "; expected 0\n";
}
From Why does double empty curly braces { { } } create a std::initializer_list with one element, not zero? I get that it is the expected behaviour of std::initializer_list so how can I redesign my class in order to have:
- default construction giving empty vector;
- braced-initializers list of value giving also an empty vector if list is empty?
NB I thought about template construction from T const (&)[N] yet I was interested in taking benefit from no-narrowing conversion enforced by std::initializer_list.
[EDIT] here is an extended example that shows how brace initialization rules confuse me (and possibily my libraries client)
#include <iostream>
#include <vector>
struct Wrap {
std::vector<double> v;
Wrap() { std::cout << "default constructor\n"; }
Wrap(std::initializer_list<double> const &l) : v(l) {
std::cout << "IL constructor\n";
}
};
int main() {
{
Wrap v = {1, 2};
std::cout << v.v.size() << "; expected 2\n";
}
{
Wrap v = {{1, 2}};
std::cout << v.v.size() << "; expected 2, braces elision?\n";
}
{
Wrap v = {1};
std::cout << v.v.size() << "; expected 1\n";
}
{
Wrap v = {{1}};
std::cout << v.v.size() << "; expected 1, braces elision?\n";
}
{
Wrap v = {};
std::cout << v.v.size()
<< "; expected 0, default ctor has precedence other IL one\n";
}
{
Wrap v = {{}};
std::cout << v.v.size() << "; expected 0, braces elision?\n";
}
}
And the obtained output:
Program returned: 0
IL constructor
2; expected 2
IL constructor
2; expected 2, braces elision?
IL constructor
1; expected 1
IL constructor
1; expected 1, braces elision?
default constructor
0; expected 0, default ctor has precedence other IL one
IL constructor
1; expected 0, braces elision?
{{}}is not an empty list. It explicitly has 1 element. Why would you want to treat it as empty? What about other 1 element lists, should they also be treated as empty? Your question doesn't make sense to me.{{}}is like saying{double{}}which is like saying{double{0.}}which is like saying{0.}. You could also pass two values:{{},{}}.initializer_list, you make list initialization special (i.e., the prefered way to initialize the object). Just remove that constructor, and the list initialization falls back to aggregate initialization, and suddenly the inner braces now initialize the first member with an empty list, just what you want.{{}}to beWrap{std::initializer_list<double>{}}and notWrapper{std::initializer_list<double>{double{}}}}.