7

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?

user541686
  • 205,094
  • 128
  • 528
  • 886

4 Answers4

3

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.

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • But a default constructor does not necessarily initialize its members. – aschepler Mar 08 '14 at 05:29
  • @aschepler but then the value initialzation case does not work. – Shafik Yaghmour Mar 08 '14 at 05:32
  • So what if you default the default ctor? Value-initialization like `auto x = Wrapper();` will zero-initialize the object, whereas default-initialization like `Wrapper x;` will default-initialize it. – dyp Mar 08 '14 at 17:59
  • @dyp hmmm, I don't see that, as far as I can tell section `12 p 6` from `N3485` doesn't say that but maybe I am mis-reading. If you can point me to the section then I will happily update my answer. – Shafik Yaghmour Mar 08 '14 at 18:50
  • `auto x = Wrapper();` (the rhs) is value-initialization [dcl.init]/11. The second bullet in [dcl.init]/8 says "if `T` is a [..] non-union class type without a user-provided [..] default constructor" which is the case if the default ctor is defaulted. The object will be zero-initialized, which means recursive zero-initialization of all subobjects (even for class types). `Wrapper x;` is default-initialization [dcl.init]/12. The first bullet in [dcl.init]/7 says, the default ctor is called. The defaulted default-ctor will perform default-init of all subobjects, which means no init here. – dyp Mar 08 '14 at 19:59
  • @dyP I see what you are saying now, let me update my answer. Thank you for pointing that out, I learned something new today. – Shafik Yaghmour Mar 09 '14 at 01:55
  • @ShafikYaghmour: Oooh wow, I didn't see this one coming... I guess it's C++11-only but it's better than nothing! Thanks a lot. – user541686 Mar 09 '14 at 02:07
2

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.

Praetorian
  • 106,671
  • 19
  • 240
  • 328
0

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.

aruisdante
  • 8,875
  • 2
  • 30
  • 37
  • You may be right that it's impossible, but I think your explanation of why it is impossible may in fact be wrong. – user541686 Mar 08 '14 at 05:26
  • I guess I simplified. As long as you haven't explicitly *not* defined a default constructor (by defining any other constructor and not defining a zero-argument one), it will happen. Shafik's answer is the correct one. – aruisdante Mar 08 '14 at 05:31
0

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.

Stuart Olsen
  • 476
  • 2
  • 7
  • Uninitialized values are pretty darn useful. They can make array allocations O(1) time instead of O(n). – user541686 Mar 08 '14 at 09:20
  • @Mehrdad Zero-initializing an array may be O(n), but with a very low constant factor. That's not to say it's _never_ a problem, but in my experience it's quite rare; how often do you write code that repeatedly allocates a large array in a tight loop? This smells like premature optimization, and there are ways to avoid the initialization cost once you've profiled, without sacrificing the well-behavedness of the common case. – Stuart Olsen Mar 08 '14 at 20:27
  • As a matter of fact, I believe you have absolutely ***zero*** evidence that this is premature optimization. – user541686 Mar 08 '14 at 20:39
  • @Mehrdad I always zero-initialize my arrays, and it has _never_ caused performance issues. That's all the evidence I need not to go polluting my program with uninitialized values just because it might spend a few fewer microseconds zeroing out memory. Without empirical evidence that the initialization negatively impacts performance, leaving values or arrays uninitialized because it seems like it should be faster is _by definition_ a premature optimization. – Stuart Olsen Mar 08 '14 at 22:23
  • No, what I'm telling you is that there is *nothing* in my question that even *hints* that **my** optimization is premature, or that I don't have empirical evidence this is necessary. In other words, you're assuming with zero evidence that you know my constraints better than I do. Piece of advice: when you don't have any evidence to believe you know people's constraints better than themselves, *don't assume so*. – user541686 Mar 08 '14 at 23:02
  • And *just for your information*, leaving some arrays uninitialized in my program gives me a 10% running time boost (from 7.3 to 6.5 seconds), which I believe is worth it. (It's also worth noting that it drops the peak working set from 3.3 GB to 3.0 GB, which I'm not exactly unhappy about.) – user541686 Mar 08 '14 at 23:23
  • @Mehrdad I did not assume that you hadn't made the appropriate measurements, nor did I assume that you _had_. Without knowing that you had found the optimization to be applicable, a footnote on the downsides and uncommon applicability of the technique after the answer to the question seemed perfectly relevant. It was meant as helpful _general_ advice about performing this optimization prematurely, not an accusation that _you_ had performed it prematurely. I'm sorry if I didn't make that clear. – Stuart Olsen Mar 09 '14 at 07:41