I recently stumbles across some problem with initializer lists. Consider a program that stores map-like data
struct MyMapLike {
MyMapLike(std::map
Since I have a map-like class, and the initializer has the abstract value of a mapping-list, I would like to use the former version
And herin lies the problem: it's up to you to supply the constructors that allow your class to be treated like a map. You called your solution a work-around, but there's nothing to work around. :)
But I think this separate need of the constructor is dead ugly.
It is, but unfortunately since it's your class, you have to specify how the initializer lists work.
You could initialize as follows:
MyMapLike maps ( { { "One", 1 }, { "Two", 2 } } );
The outer ( ... )
are now clearly simply to surround the constructor args, and it's easier to see that the outermost { ... }
defines the 'list'.
It's still a bit verbose, but it avoids the situation where {
is being used to do two different things, which affects readability.
What do you think about the former and latter form of initialization? Does it make sense to be required to have extra braces in this case?
I prefer the first form because it has a clean class interface. The constructor taking an initializer list pollutes the interface and offers little in return.
The extra braces are something we'll get used to. Just like we got used to C++'s other quirks.
What do you think about the former and latter form of initialization? Does it make sense to be required to have extra braces in this case?
I think so. I think it would permit too much ambiguity to allow a constructor be called not just when the syntax matches that constructor, but when the syntax matches some constructor of the single argument to the constructor, and so on recursively.
struct A { int i; };
struct B { B(A) {} B(int) {} };
struct C { C(B) {} };
C c{1};
Do you consider the requirement for addition of an initializer list constructor in this case bad?
No. It lets you get the syntax you want but without creating the problems that arise if we make the compiler search harder for a constructor to use.
I agree that it's ugly. I usually scrap initializer lists once I move beyond very simple cases as they're quite limited. For your case I'd go with boost::assign, which while not as terse gives better flexibility and control.
Wouldn't something like this give the desired effect (MyMapLike
can be constructed in any way that std::map
can, but does not implicitly convert to std::map
)?
struct MyMapLike : private std::map<std::string, int>
{
using map::map;
};
If it absolutely positively has to be a member, maybe use constructor perfect forwarding (I'm not sure about the exact syntax) along the lines of:
struct MyMapLike
{
template<typename... Initializers>
MyMapLike(Initializers... init, decltype(new std::map<std::string, int>(...init)) = 0)
: data(...init)
{ }
private:
std::map<std::string, int> data;
};