Since C++11, the Standard Library containers and std::string
have constructors taking an initializer-list. This constructor takes precedence over other constructors (even, as pointed out by @JohannesSchaub-litb in the comments, even ignoring other "best match" criteria). This leads to a few well-known pitfalls when converting all parenthesized ()
forms of constructors to their braced versions {}
#include <algorithm>
#include <iostream>
#include <iterator>
#include <vector>
#include <string>
void print(std::vector<int> const& v)
{
std::copy(begin(v), end(v), std::ostream_iterator<int>(std::cout, ","));
std::cout << "\n";
}
void print(std::string const& s)
{
std::cout << s << "\n";
}
int main()
{
// well-known
print(std::vector<int>{ 11, 22 }); // 11, 22, not 11 copies of 22
print(std::vector<int>{ 11 }); // 11, not 11 copies of 0
// more surprising
print(std::string{ 65, 'C' }); // AC, not 65 copies of 'C'
}
I couldn't find the third example on this site, and the thing came up in the Lounge<C++> chat (in discussion with @rightfold, @Abyx and @JerryCoffin), The somewhat surprising thing is that converting the std::string
constructor taking a count and a character to use {}
instead of ()
, changes its meaning from n
copies of the character to the n
-th character (typically from the ASCII table) followed by the other character.
This is not caught by the usual brace prohibition on narrowing conversions, because 65 is a constant expression that can be represented as a char and will retain its original value when converted back to int (§8.5.4/7, bullet 4) (thanks to @JerryCoffin).
Question: are there more examples lurking in the Standard Library where converting a ()
style constructor to {}
style, is greedily matched by an initializer-list constructor?
I assume, with your examples for std::vector<int>
and std::string
you meant to also cover the other containers, e.g., std::list<int>
, std::deque<int>
, etc. which have the same problem, obviously, as std::vector<int>
. Likewise, the int
isn't the only type as it also applies to char
, short
, long
and their unsigned
version (possibly a few other integral types, too).
I think there is also std::valarray<T>
but I'm not sure if T
is allowed to be integral type. Actually, I think these have different semantics:
std::valarray<double>(0.0, 3);
std::valarray<double>{0.0, 3};
There are a few other standard C++ class templates which take an std::initializer_list<T>
as argument but I don't think any of these has an overloaded constructor which would be used when using parenthesis instead of braces.
Just searching for the occurence of initializer_list
.
All sequences, they are have the constructors like that of vector:
valarray
basic_string
Unordered collections, there is a constructor which takes an integer to determine the initial bucket count.
I think that's all of it.
#include <unordered_set>
#include <iostream>
int main() {
std::unordered_set<int> f (3);
std::unordered_set<int> g {3};
std::cout << f.size() << "/" << g.size() << std::endl; // prints 0/1.
}
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