Why isn't std::initializer_list
a core-language built-in?
It seems to me that it's quite an important feature of C++11 and yet it doesn't have its own reserved keyword (or something alike).
Instead, initializer_list
it's just a template class from the standard library that has a special, implicit mapping from the new braced-init-list {...}
syntax that's handled by the compiler.
At first thought, this solution is quite hacky.
Is this the way new additions to the C++ language will be now implemented: by implicit roles of some template classes and not by the core language?
Please consider these examples:
widget<int> w = {1,2,3}; //this is how we want to use a class
why was a new class chosen:
widget( std::initializer_list<T> init )
instead of using something similar to any of these ideas:
widget( T[] init, int length ) // (1)
widget( T... init ) // (2)
widget( std::vector<T> init ) // (3)
- a classic array, you could probably add
const
here and there - three dots already exist in the language (var-args, now variadic templates), why not re-use the syntax (and make it feel built-in)
- just an existing container, could add
const
and&
All of them are already a part of the language. I only wrote my 3 first ideas, I am sure that there are many other approaches.
There were already examples of "core" language features that returned types defined in the
std
namespace.typeid
returnsstd::type_info
and (stretching a point perhaps)sizeof
returnsstd::size_t
.In the former case, you already need to include a standard header in order to use this so-called "core language" feature.
Now, for initializer lists it happens that no keyword is needed to generate the object, the syntax is context-sensitive curly braces. Aside from that it's the same as
type_info
. Personally I don't think the absence of a keyword makes it "more hacky". Slightly more surprising, perhaps, but remember that the objective was to allow the same braced-initializer syntax that was already allowed for aggregates.So yes, you can probably expect more of this design principle in future:
std
rather than as builtins.Hence:
std
.What it comes down to, I think, is that there is no absolute division in C++ between the "core language" and the standard libraries. They're different chapters in the standard but each references the other, and it has always been so.
There is another approach in C++11, which is that lambdas introduce objects that have anonymous types generated by the compiler. Because they have no names they aren't in a namespace at all, certainly not in
std
. That's not a suitable approach for initializer lists, though, because you use the type name when you write the constructor that accepts one.Not only can it work completely in the standard library. Inclusion into the standard library does not mean that the compiler can not play clever tricks.
While it may not be able to in all cases, it may very well say: this type is well known, or a simple type, lets ignore the
initializer_list
and just have a memory image of what the initialized value should be.In other words
int i {5};
can be equivalent toint i(5);
orint i=5;
or evenintwrapper iw {5};
Whereintwrapper
is a simple wrapper class over an int with a trivial constructor taking aninitializer_list
It's not part of the core language because it can be implemented entirely in the library, just line
operator new
andoperator delete
. What advantage would there be in making compilers more complicated to build it in?This is indeed nothing new and how many have pointed out, this practice was there in C++ and is there, say, in C#.
Andrei Alexandrescu has mentioned a good point about this though: You may think of it as a part of imaginary "core" namespace, then it'll make more sense.
So, it's actually something like:
core::initializer_list
,core::size_t
,core::begin()
,core::end()
and so on. This is just an unfortunate coincidence thatstd
namespace has some core language constructs inside it.The C++ Standard Committee seems to prefer not to add new keywords, probably because that increases the risk of breaking existing code (legacy code could use that keyword as the name of a variable, a class, or whatever else).
Moreover, it seems to me that defining
std::initializer_list
as a templated container is quite an elegant choice: if it was a keyword, how would you access its underlying type? How would you iterate through it? You would need a bunch of new operators as well, and that would just force you to remember more names and more keywords to do the same things you can do with standard containers.Treating an
std::initializer_list
as any other container gives you the opportunity of writing generic code that works with any of those things.UPDATE:
To begin with, all others containers have methods for adding, removing, and emplacing elements, which are not desirable for a compiler-generated collection. The only exception is
std::array<>
, which wraps a fixed-size C-style array and would therefore remain the only reasonable candidate.However, as Nicol Bolas correctly points out in the comments, another, fundamental difference between
std::initializer_list
and all other standard containers (includingstd::array<>
) is that the latter ones have value semantics, whilestd::initializer_list
has reference semantics. Copying anstd::initializer_list
, for instance, won't cause a copy of the elements it contains.Moreover (once again, courtesy of Nicol Bolas), having a special container for brace-initialization lists allows overloading on the way the user is performing initialization.
This is nothing new. For example,
for (i : some_container)
relies on existence of specific methods or standalone functions insome_container
class. C# even relies even more on its .NET libraries. Actually, I think, that this is quite an elegant solution, because you can make your classes compatible with some language structures without complicating language specification.