I had a fairly quick question. The std::vector provides the following two constructors:
explicit vector( const Allocator& alloc = Allocator()); // default constructor
explicit vector( size_type count, // fill constructor
const T& value = T(),
const Allocator& alloc = Allocator());
Is there any particular reason the default constructor is not implemented with a default value of 0 for the first parameter in the fill constructor? I can imagine there must be a reason but I can't immediately see one.
Because you then can't pass just an allocator, without providing the count or default element (aka value) as well?
Putting count to 0 will result in ambiguity error.
Would be much more simpler if C++ had named params like Python. Boost has such a library, but again that incurs some runtime overhead :( (can't remember how much right now) I often used this Lib in testing, but not where the performance is important...
The reason is that the constructors place different requirements on the type contained in the vector. To use the second one, the type must copy-constructible, and if you use the default argument for value, it must be default-constructible too. The first constructor places no such requirements on the contained type.
Note that the constructors you're showing in the question only existed until C++11. There, it was sufficient to differentiate those two scenarios (since any type stored in a std::vector had to be copy-constructible). C++11 introduced move semantics, and the second constructor was split further:
explicit vector(size_type count);
vector(
size_type count,
const T& value,
const Allocator& alloc = Allocator()
);
That's because std::vector no longer requires its contained types to be copy-constructibe; move-constructibility is enough. Therefore the count-only constructor requires default constructibility (but not copy constructibility), and the count + prototype constructor requires copy constructibility (but not default constructibility).
The evolution of std::vector constructors is really quite complex. See their page on cppreference to see how much they've evolved. That evolution includes adding an optional allocator parameter to the count-only constructor in C++14, which was (I assume) mistakenly omitted.
Related
I'm asking this because im developing a class library.
(As i can see in cplusplus.com) in C++98 we had only one version from the std::vector::resize funcion:
void resize (size_type n, value_type val = value_type());
this way the function works if you want to specify the second argument or not. But in C++11 they change it to 2 different version:
void resize (size_type n);
void resize (size_type n, const value_type& val);
Is any special reason for this?
For std::vector::resize, before C++11, the default argument of val is always value-initialized, the appended elements are copied from it, as the effect they're value-initialized too. Since C++11 this could be avoided by providing a custom Allocator.
additional default-inserted elements are appended (since C++11)
(emphasis mine)
By default, this will call placement-new, as by ::new((void*)p) T() (that is, value-initialize the object pointed to by p). If value-initialization is undesirable, for example, if the object is of non-class type and zeroing out is not needed, it can be avoided by providing a custom Allocator::construct.
When I was trying to understand the different types of initialization in modern C++, I came across the initialization of std::vector<T> with an initialization list. To allow initialization with initializer list data structure such as std::vector<T> should have a constructor that accepts initializer as the parameter. I observed is that std::vector<T> accepts the initializer list by copy not as a reference, accepting by copy when we have a huge number of elements can be very expensive. Why is it so is there any particular reason why the initializer list for taking it as a copy instead of reference?
From https://en.cppreference.com/w/cpp/container/vector/vector
vector( std::initializer_list<T> init, … ); (9) (since C++11)
Why not?
vector( std::initializer_list<T>& init, … );
std::initializer_list doesn't copy underlying objects.
As you can read here:
Copying a std::initializer_list does not copy the underlying objects.
So it doesn't really waste a lot of memory or time.
According to cppreference.com,
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 lists may be implemented as a pair of pointers or pointer and length. Copying a std::initializer_list does not copy the underlying objects.
So, despite the fact that the std::initializer_list creates a temporary array, the elements of this array are not copied even if the std::initializer_list is passed by value. But if you like, you can accept objects of this type by a constant reference. This works great in my projects. For example:
auto some_function(const std::initializer_list<T>& list);
I absolutely love the way Xcode offers insight into possible available member functions of the language and would prefer to use it relative to, say, text mate, if not for an oddity i noticed today.
When string s = "Test string"; the only available substr signature is as shown
From what i understand however, and what i see online the signature should be
string substr ( size_t pos = 0, size_t n = npos ) const;
Indeed s.substr(1,2); is both understood and works in Xcode.
Why does it not show when i try to method complete? (Ctrl-Space)
Xcode is performing the completion correctly, but it's not what you expect. You've actually answered the question yourself unknowingly. The function signature for string's substr() method, just as you said, is:
string substr ( size_t pos = 0, size_t n = npos ) const;
All arguments to substr() have default assignments, therefore to Xcode, s.substr() (with no arguments) is the valid code completion to insert because it's really s.substr(0, s.npos). You can confirm this with any number of standard C++ functions with default arguments. The easiest place to see this is with any STL container constructor.
Take for instance a vector. We all know that vectors can take an Allocator, but the default argument assigned Allocator is "good enough" for most casual uses. Sure enough, two of the signatures for vector constructors are:
explicit vector ( const Allocator& = Allocator() );
explicit vector ( size_type n, const T& value= T(), const Allocator& = Allocator() );
In both cases, the Allocator argument has a default assignment, and in the second, the T default value has a default assignment. Now, take a look at what Xcode suggests when constructing a vector:
The suggestion with no argument list is actually the constructor that takes just an Allocator. The suggestion that takes just a size_type is actually the constructor that takes a size_type, T, and Allocator.
Depending on how you think about this, it may or may not be an Xcode bug. Ideally, you want to see completions with default arguments for simpler functions like substr(), but for STL container constructors, you probably almost never want to see them. Perhaps it could be an option, but I wouldn't expect to see this corrected. I'd happily dup a radar with you though.
If I create a vector like vector<myClass> v(10);
what is the default value of each element?
Also, what if it is a vector<myUnion> v(10) ?
The constructor of std::vector<> that you are using when you declare your vector as
vector<myClass> v(10);
actually has more than one parameter. It has three parameters: initial size (that you specified as 10), the initial value for new elements and the allocator value.
explicit vector(size_type n, const T& value = T(),
const Allocator& = Allocator());
The second and the third parameters have default arguments, which is why you were are able to omit them in your declaration.
The default argument value for the new element is the default-contructed one, which in your case is MyClass(). This value will be copied to all 10 new elements by means of their copy-constructors.
What exactly MyClass() means depends on your class. Only you know that.
P.S. Standard library implementations are allowed to use function overloading instead of default arguments when implementing the above interface. If some implementation decides to use function overloading, it might declare a constructor with just a single parameter (size) in std::vector. This does not affect the end result though: all vector elements should begin their lives as if they were value-initialized.
vector<myClass> v;
its a empty vector with size and capacity as 0.
The answer to your second question is similar; vector<myUnion> v(10) will create an array of 10 myUnions initialized with their default constructor. However note that: 1) Unions can't have members with constructors, copy constructors or destructors, as the compiler won't know which member to construct, copy or destroy, and 2) As with classes and structs, members with built-in type such as int will be initialized as per default, which is to say not at all; their values will be undefined.
I like to initialize 2-dimensional arrays as vector<vector<int> >(x,y). x is passed to vector<vector<int> >'s constructor and y is passed to vector<int>'s constructor, x times. Although this seems to be forbidden by C++03, because the constructor is explicit, it always works, even on Comeau. I can also call vector::assign like this. But, for some reason, not vector::push_back.
vector< vector< int > > v( 20, 40 ); // OK: convert 40 to const T&
v.assign( 30, 50 ); // OK
v.push_back( 2 ); // error: no conversion, no matching function, etc.
Are the first two examples actually compliant for some reason? Why can I convert 40 and 50 but not 2?
Epilogue: see http://gcc.gnu.org/onlinedocs/libstdc++/ext/lwg-defects.html#438 for why most compilers allow this, but the standard is shifting the other way.
Your assumption about Comeau implicitly calling an explicit constructor is most likely incorrect. The behavior is indeed broken, but the problem is different.
I suspect that this is a bug in the implementation of Standard Library that comes with Comeau, not with core Comeau compiler itself (although the line is blurry in this case).
If you build a quick dummy class that has constructor properties similar to std::vector and try the same thing, you'll discover that the compiler correctly refuses to perform construction.
The most likely reason why it accepts your code is the well-known formal ambiguity of two-parameter constructor of std::vector. It can be interpreted as (size, initial value) constructor
explicit vector(size_type n, const T& value = T(),
const Allocator& = Allocator());
or as (begin, end) template constructor with the latter accepting two iterators
template <class InputIterator>
vector(InputIterator first, InputIterator last,
const Allocator& = Allocator());
The standard library specification explicitly states that when two integral values are used as arguments, the implementation must make sure somehow that the formed constructor is selected, i.e. (size, initial value). How it is done - doesn't matter. It can be done at the library level, it can be hardcoded in the core compiler. It can be done in any other way.
However, in response to ( 20, 40 ) arguments Comeau compiler appears to erroneously select and instantiate the latter constructor with InputIterator = int. I don't know how it manages to compile the specialized version of the constructor, since integral values can't and won't work as iterators.
If you try this
vector< vector< int > > v( 20U, 40 );
you'll discover that the compiler reports an error now (since it can no longer use the two-iterator version of the constructor) and the explicit on the first constructor prevents it from converting 40 to a std::vector.
The same thing happens with assign. This certainly a defect of Comeau implementation, but, once again, experiments show that most likely the required behavior was supposed to be enforced at the library level (the core compiler seems to work OK), and somehow it got done incorrectly.
On the second thought, I see that the main idea in my explanation is correct, but the details are wrong. Also, I can be wrong about calling it a problem in Comeau. It is possible that Comeau is right here.
The standard says in 23.1.1/9 that
the constructor
template <class InputIterator>
X(InputIterator f, InputIterator l, const Allocator& a = Allocator())
shall have the same effect as:
X(static_cast<typename X::size_type>(f),
static_cast<typename X::value_type>(l),
a)
if InputIterator is an integral type
I suspect that if the above is interpreted literally, the compiler is allowed to assume that an explicit static_cast is implied there (well... so to say), and the code is legal for the same reason static_cast< std::vector<int> >(10) is legal, despite the corresponding constructor's being explicit. The presence of static_cast is what makes it possible for the compiler to use the explicit constructor.
If the behavior of Comeau compiler is correct (and I suspect that it is in fact correct, as required by the standard), I wonder whether this was the intent of the committee to leave such a loophole open, and allow implementations to work arount the explicit restriction possibly present on the constructor of vector element.
Are the first two examples actually compliant for some reason?
They are not compliant on the compiler I just tried. (gcc 4.4.1)
Why can I convert 40 and 50 but not 2?
Since the first two lines are not consistent with the standard, only Comeau may know why their inconsistency is inconsistent.
It is not an accident that the standard requires explicit conversions from int types to arbitrary vectors. It is done to prevent confusing code.
vector< vector< int > > v( 20, 40 ); use a constructor that you might not be familiar with. The constructor is called here is vector(iterator start, iterator end);
Internally, it specializes to an int iterator, so the first parameter is treated as count, and second parameter is the value to initialize the vector. Because there is a cast when assign the second parameter to a vector value, so the constructor of the inner vector<T>(int, const T&) will be called with value 40. Therefore, the inner vector is constructed with 40 0's.
Your examples aren't compliant. The constructor is explicit as you said, so you not allowed to pass an int (y) instead of a vector for the constructor (and y is not passed "x times" to vector constructor: the second paramater is only created once, to initilialize the inserted objects).
Your examples don't work under gcc (4.4 & 4.5).