Consider this class:
class Buffer { public: Buffer(size_t capacity); Buffer(std::initializer_list<int> values); };
You can create an uninitialized buffer with a particular capacity, or you can create an initialized buffer.
The one-parameter constructor also serves as a conversion constructor, resulting in the following:
Buffer buffer(24); // create a buffer of size 24 Buffer buffer({ 1, 3, 5 }); // create an initialized 3-byte buffer
Okay, those don’t look too bad. But you also get this:
Buffer buffer = 24; // um... Buffer buffer = { 1, 3, 5 };
These are equivalent to the first two versions, but you have to admit that the = 24
version looks really weird.
You also get this:
extern void Send(Buffer const& b); Send('c'); // um...
This totally compiles, but it doesn’t send the character 'c'
, which is what it looks like. Instead, it creates an uninitialized buffer of size 0x63 = 99
and sends it.
If this is not what you intended, then you would be well-served to use the explicit
keyword to prevent a constructor from being used as conversion constructions.
class Buffer
{
public:
explicit Buffer(size_t capacity);
Buffer(std::initializer_list<int> values);
};
I made the first constructor explicit, since I don’t want you to pass an integer where a buffer is expected. However, I left the initializer list as a valid conversion constructor because it seems reasonable to let someone write
Send({ 1, 2, 3 });
I strongly suggest we have a new “C++” tag dedicated to the documentation of the hijinks of the C++ language.
For this reason, I habitually put “explicit” on every constructor, except copy constructors. (If you put explicit on a copy constructor, your object becomes non-copyable, defeating the point of the constructor.)
This exact issue with a very similar class caused me so much grief in the past.
Even worse I think
Buffer buffer = { 3 };
will try and use the first one or something? This exact issue is what killed my interest in C++ (after a week or two of debugging segfaults). Just so unhappy when I figured out it was my fault for not declaring the constructor explicit. I haven’t taken a C++ job since then.
Nope! According to https://en.cppreference.com/w/cpp/language/list_initialization, an initializer_list constructor is preferred to a non-initializer_list constructor (see the sixth bullet under "Explanation"). This fits with the C++ heuristic that you want to use curly brackets when listing out the values in your object, but parentheses when specifying parameters to a (constructor) method. Thus: <code>
I love this language, but that might be the Stockholm Syndrome speaking.
I stumbled upon this problem years ago since std::vector has a similar interface (i.e. one with count and one with an initializer_list):
<code>
The curly brace initialization is unfortunately not a drop in replacement. std::vector should have added and support for an unambiguous initialization for the 'count' overload; e.g. using an extra enumerate argument.
I like C++ but the language has sharp edges.
Another unfortunate issue with std::vector is that there is no reserve constructor.