Search code examples
c++visual-c++initializationvalue-initialization

How to make a user-defined type initialize *exactly* like a built-in type?


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?


Solution

  • 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.