Header <boost/utility/value_init.hpp>

Contents

Rationale
Introduction
Types
Acknowledgements


Rationale

Constructing and initializing objects in a generic way is difficult in C++. The problem is that there are several different rules that apply for initialization. Depending on the type, the value of a newly constructed object can be zero-initialized (logically 0), default-constructed (using the default constructor), or indeterminate. When writing generic code, this problem must be addressed. value_initialized provides a solution with consistent syntax for value initialization of scalar, union and class types.

Introduction

The C++ standard [1] contains the definitions of zero-initialization and default-initialization. Informally, zero-initialization means that the object is given the initial value 0 (converted to the type) and default-initialization means that POD [2] types are zero-initialized, while class types are initialized with their corresponding default constructors. A declaration can contain an initializer, which specifies the object's initial value. The initializer can be just '()', which states that the object shall be default-initialized (but see below). However, if a declaration has no initializer and it is of a non-const, non-static POD type, the initial value is indeterminate:(see §8.5 for the accurate definitions).

int x ; // no initializer. x value is indeterminate.
std::string s ; // no initializer, s is default-constructed.

int y = int() ;
// y is initialized using copy-initialization
// but the temporary uses an empty set of parentheses as the initializer,
// so it is default-constructed.
// A default constructed POD type is zero-initialized,
// therefore, y == 0.

void foo ( std::string ) ;
foo ( std::string() ) ;
// the temporary string is default constructed
// as indicated by the initializer ()

value-initialization

The first Technical Corrigendum for the C++ Standard (TC1), whose draft was released to the public in November 2001, introduced Core Issue 178 (among many other issues, of course).

That issue introduced the new concept of value-initialization (it also fixed the wording for zero-initialization). Informally, value-initialization is similar to default-initialization with the exception that in some cases non-static data members and base class sub-objects are also value-initialized. The difference is that an object that is value-initialized won't have (or at least is less likely to have) indeterminate values for data members and base class sub-objects; unlike the case of an object default constructed. (see Core Issue 178 for a normative description).

In order to specify value-initialization of an object we need to use the empty-set initializer: ().

(but recall that the current C++ Standard states that '()' invokes default-initialization, not value-initialization)

As before, a declaration with no intializer specifies default-initialization, and a declaration with a non-empty initializer specifies copy (=xxx) or direct (xxx) initialization.

template<class T> void eat(T);
int x ; // indeterminate initial value.
std::string s; // default-initialized.
eat ( int() ) ; // value-initialized
eat ( std::string() ) ; // value-initialied

value-initialization syntax

Value initialization is specified using (). However, the empty set of parentheses is not permitted by the syntax of initializers because it is parsed as the declaration of a function taking no arguments:

int x() ; // declares function int(*)()
int y ( int() ) ; // decalares function int(*)( int(*)() )

Thus, the empty () must be put in some other initialization context.

One alternative is to use copy-initialization syntax:

int x = int() ;

This works perfectly fine for POD types. But for non-POD class types, copy-initialization searches for a suitable constructor, which could be, for instance, the copy-constructor (it also searches for a suitable conversion sequence but this doesn't apply in this context). For an arbitrary unknown type, using this syntax may not have the value-initialization effect intended because we don't know if a copy from a default constructed object is exactly the same as a default constructed object, and the compiler is allowed (in some cases), but never required to, optimize the copy away.

One possible generic solution is to use value-initialization of a non static data member:

template<class T> 
struct W
{
// value-initialization of 'data' here.
W() : data() {}
T data ;
} ;
W<int> w ;
// w.data is value-initialized for any type.

This is the solution supplied by the value_initialized<> template class.

Types

template class value_initialized<T>

namespace boost {

template<class T>
class value_initialized
{
public :
value_initialized() : x() {}
operator T&() const { return x ; }
T& data() const { return x ; }

private :
unspecified x ;
} ;

template<class T>
T const& get ( value_initialized<T> const& x )
{
return x.data() ;
}

template<class T>
T& get ( value_initialized<T>& x )
{
return x.data() ;
}

} // namespace boost

An object of this template class is a T-wrapper convertible to 'T&' whose wrapped object (data member of type T) is value-initialized upon default-initialization of this wrapper class:

int zero = 0 ;
value_initialized<int> x ;
assert ( x == zero ) ;

std::string def ;
value_initialized< std::string > y ;
assert ( y == def ) ;

The purpose of this wrapper is to provide a consistent syntax for value initialization of scalar, union and class types (POD and non-POD) since the correct syntax for value initialization varies (see value-initialization syntax)

The wrapped object can be accessed either through the conversion operator T&, the member function data(), or the non-member function get():

void watch(int);
value_initialized<int> x;

watch(x) ; // operator T& used.
watch(x.data());
watch( get(x) ) // function get() used

Both const and non-const objects can be wrapped. Mutable objects can be modified directly from within the wrapper but constant objects cannot:

value_initialized<int> x ; 
static_cast<int&>(x) = 1 ; // OK
get(x) = 1 ; // OK

value_initialized<int const> y ;
static_cast<int&>(y) = 1 ; // ERROR: cannot cast to int&
static_cast<int const&>(y) = 1 ; // ERROR: cannot modify a const value
get(y) = 1 ; // ERROR: cannot modify a const value

Warning:

Both the conversion operator and the data() member function are const in order to allow access to the wrapped object from a constant wrapper:

void foo(int);
value_initialized<int> const x ;
foo(x);

But notice that this conversion operator is to T& although it is itself const. As a consequence, if T is a non-const type, you can modify the wrapped object even from within a constant wrapper:

value_initialized<int> const x_c ;
int& xr = x_c ; // OK, conversion to int& available even though x_c is itself const.
xr = 2 ;

The reason for this obscure behavior is that some commonly used compilers just don't accept the following valid code:

struct X
{
operator int&() ;
operator int const&() const ;
};
X x ;
(x == 1 ) ; // ERROR HERE!

These compilers complain about ambiguity between the conversion operators. This complaint is incorrect, but the only workaround that I know of is to provide only one of them, which leads to the obscure behavior just explained.

Recommended practice: The non-member get() idiom

The obscure behavior of being able to modify a non-const wrapped object from within a constant wrapper can be avoided if access to the wrapped object is always performed with the get() idiom:

value_initialized<int> x ;
get(x) = 1 ; // OK

value_initialized<int const> cx ;
get(x) = 1 ; // ERROR: Cannot modify a const object

value_initialized<int> const x_c ;
get(x_c) = 1 ; // ERROR: Cannot modify a const object

value_initialized<int const> const cx_c ;
get(cx_c) = 1 ; // ERROR: Cannot modify a const object

References

[1] The C++ Standard, ISO/IEC 14882:98
[2] Plain Old Data

Acknowledgements

value_initialized was developed by Fernando Cacciola, with help and suggestions from David Abrahams and Darin Adler.
Special thanks to Björn Karlsson who carefully edited and completed this documentation.
 

Revised 19 September 2002

© Copyright boost.org 2002. Permission to copy, use, modify, sell and distribute this document is granted provided this copyright notice appears in all copies. This document is provided "as is" without express or implied warranty, and with no claim as to its suitability for any purpose.

Developed by Fernando Cacciola, the latest version of this file can be found at www.boost.org, and the boost discussion list at www.yahoogroups.com/list/boost.