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?
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.
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.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: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:
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):and we can see this has different results for classes than built-in types from paragraph 7 which says:
and includes the following bullets:
and if we look at paragraph 11 for the second case
Wrapper<unsigned int>()
it says:and then back to paragraph 8:
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.
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):
You can still set the value during construction via aggregate-initialization:
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.