I would like to make a type that wraps a numeric type (and provides additional functionality).
Furthermore, I need the number and the wrapper to be both implicitly convertible to each other.
So far I have:
template<class T>
struct Wrapper
{
T value;
Wrapper() { }
Wrapper(T const &value) : value(value) { }
// ... operators defined here ...
};
It's almost good, but it doesn't quite behave the same as a built-in type:
#include <iostream>
int main()
{
unsigned int x1, x2 = unsigned int();
Wrapper<unsigned int> y1, y2 = Wrapper<unsigned int>();
std::cerr << x1 << std::endl; // uninitialized, as expected
std::cerr << y1.value << std::endl; // uninitialized, as expected
std::cerr << x2 << std::endl; // zero-initialized, as expected
std::cerr << y2.value << std::endl; // uninitialized!?!
}
Is there any way for me to design the Wrapper
such that statements like
Wrapper<unsigned int> y2 = Wrapper<unsigned int>();
initialize the value
inside, but without forcing statements like
Wrapper<unsigned int> y1;
to also do the same?
In other words, is it possible to make a type that behaves exactly the same as a built-in type in terms of initialization?
Updated Answer
Okay, so as dyp points out, I and everyone else was wrong. You can achieve what you want to do by = default
with the default constructor:
Wrapper() = default ;
^^^^^^^^^
This works because without an initializer you obtain the same behavior I outline before but when you use value initialization the behavior changes as outlined in paragraph 8:
— if T is a (possibly cv-qualified) non-union class type without a user-provided or deleted default constructor, then the object is zero-initialized and, if T has a non-trivial default constructor, default-initialized;
Original Answer
I don't think there is a way to make this work the way you would like. Class types act differently that builtin types we can see this from the draft standard section 8.5
Initializers paragraph 12 which says (emphasis mine going forward):
If no initializer is specified for an object, the object is default-initialized; if no initialization is performed, an object with automatic or dynamic storage duration has indeterminate value. [ Note: Objects with static or thread storage duration are zero-initialized, see 3.6.2. —end note ]
and we can see this has different results for classes than built-in types from paragraph 7 which says:
To default-initialize an object of type T means:
and includes the following bullets:
— if T is a (possibly cv-qualified) class type (Clause 9), the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);
— if T is an array type, each element is default-initialized;
— otherwise, no initialization is performed.
and if we look at paragraph 11 for the second case Wrapper<unsigned int>()
it says:
An object whose initializer is an empty set of parentheses, i.e., (), shall be value-initialized.
and then back to paragraph 8:
To value-initialize an object of type T means:
— if T is a (possibly cv-qualified) class type (Clause 9) with either no default constructor (12.1) or a default constructor that is user-provided or deleted, then the object is default-initialized; [...]
So we end up with the same behavior.
Both Praetorian and aschepler gave you options that work slightly differently but appear to achieve the behavior you would like just not with the same syntax.
I don't think there's any way to achieve what you're looking for. As soon as you define a default constructor for a class that will be called whether you provide or omit parentheses when defining an instance of the class.
You can get kinda close by declaring the following constructor; the variable definition will require an empty pair of braces to achieve value initialization.
Wrapper(std::initializer_list<std::initializer_list<T>> /*unused*/) : value() {}
auto y3 = Wrapper<unsigned int>({}); // y3.value will be value initialized
Live demo
But I'd sooner drop the requirement for implicit conversion to Wrapper
, and keep the class an aggregate, than implement the solution above.
Unfortunately, not that I can think of. C++ implicitly converts class_type name
to call the default constructor. You would have to make the default constructor do what you'd expect an un-initialized primitive type to do.
If you remove the user-provided constructor, you can leave the member uninitialized when default-constructing, or value-initialize the wrapper and in doing so zero-initialize its storage (and therefore its member):
unsigned int x1, x2 {}; // One uninitialized, one value-initialized
Wrapper<unsigned int> y1, y2 {}; // Ditto
You can still set the value during construction via aggregate-initialization:
Wrapper<int> z {42};
At any rate, this is largely unnecessary; uninitialized values are rarely useful except to introduce subtle, difficult-to-reproduce bugs. I would recommend value-initializing the member either in the default constructor or in the member declaration.