Document number: N3047=10-0037
Project: Programming Language C++, Library Working Group
Author: Daniel Krügler
Date: 2010-03-01

Fixing is_constructible and is_explicitly_convertible

Introduction

The is_constructible component is a fundamental type property trait that describes whether some type T can be constructed from a set of other types Args

  template <class T, class... Args> 
  struct is_constructible;

This trait is also used to specify semantics of other standard components within the allocator framework and will probably help to specify std::bind as part of library issue 817. During it's initial specification by N2984 an expression-based approach was used:

Given the following function prototype:

  template <class T>
  typename add_rvalue_reference<T>::type create();

the predicate condition for a template specialization is_constructible<T, Args...> shall be satisfied if and only if the following expression CE would be well-formed:

The special handling of the 1:1 conversion case using static_cast is necessary, because otherwise the expression would be interpreted equivalent to a C cast (5.4 [expr.cast]) and would thus accept unwanted const-breaking casts or reinterpreting casts as well.

The special unary transformation case also provides a natural replacement for the concept ExplicitlyConvertible<T, U> as a C++ type trait and was thus recommended to be added as well. A simple but sufficient definition given the above definition of is_constructible would look like this:

  template <class T, class U> 
  using is_explicitly_convertible = is_constructible<U, T>;

This definition of the is_explicitly_convertible trait has some remarkable properties: In fact, it is a perfect refinement of the is_convertible trait, because it evaluates to true for all expressions, for which is_convertible evaluates to true, including the special corner cases, namely that cv void can be converted to cv void. It extends this corner-case by evaluating to true for all conversions of any expression of type T to cv void (5.2.9 [expr.static.cast]).

Alas, there is a downside of the current definition of the is_constructible trait, as described by library issue 1260: The need to fall back to a static_cast in the 1:1 conversion will also allow for even more explicitness during a construction, e.g. the conversion of void* to int* or a down-cast within a class hierarchy. And these conversions would not be well-formed during a normal construction as part of a member initializer or of any variable definition like this:

	T t(u);

where u is a value of type U. So, in fact, the is_constructible trait would return false positives if testing for a well-formed direct initialization and this must be fixed.

Discussion

The currently proposed resolution of 1260 suggests the following wording change by replacing the expression-based approach by a more appropriate variable-definition:

Given the following function prototype:

  template <class T>
  typename add_rvalue_reference<T>::type create();

the predicate condition for a template specialization is_constructible<T, Args...> shall be satisfied if and only if the following expression CEvariable definition would be well-formed:

This approach would indeed solve the overshooting explicitness, but it has the following disadvantages:

  1. It has the effect that any DefaultConstructible type T that requires value-initialization, like const int, will not satisfy the is_constructible predicate condition. Nevertheless these types are usually considered as "constructible". It is worth mentioning that the currently proposed wording to define the yet missing DefaultConstructible requirements as of library issue 724 was written to ensure that every const T for which T satisfies the DefaultConstructible requirements does also satisfy the DefaultConstructible requirements. This is realized by using different symbols for the constructed entities.
  2. It has the unfortunate effect, that is_explicitly_convertible is no longer a refinement of is_convertible, because some corner conversion cases will no longer be well-formed, i.e. all conversions to cv void.

Fortunately, it is rather easy to fix the first problem by taking advantage of the fact that a pack expansion with an empty parameter pack is still evaluated as a variable definition (and not as a function declaration). Core language experts confirmed that the syntactic disambiguation must be done with the original concrete tokens. This would simplify and unify the variable definition to:

  T t(create<Args>()...);

with the wanted outcome that const types are also supported as being default-constructible. Another important aspect we would like to ensure is that this syntax also works with value-initialization of arrays. While value-initialization of arrays is defined since C++03, it was not really clear that this would be possible with the () syntax. Fortunately, the bulleted list of [dcl.init]/16 has been reworded for C++0x to actively support this construction syntax for arrays as well, see N3035.

The remaining question is, in which way the also affected is_explicitly_convertible trait should be repaired. The basic choices are:

  1. Fix is_explicitly_convertible by returning to the current static_cast expression, no longer making is_explicitly_convertible dependent on is_constructible.
  2. Remove is_explicitly_convertible from the standard.

The first choice was considered, but it turned out that there exist quite different understandings of what "explicitly convertible" should actually mean. While some believe that static_cast correctly expresses this, others believed that the fixed is_constructible would provide a better meaning for is_explicitly_convertible as well. Therefore this paper recommends to remove the is_explicitly_convertible from the working draft. This should do no harm now, because nothing depends on that special definition yet. And if it turns out, that the trait would still be useful, it could be added in another revision of the standard.

What about list-initialization?

Using the braced-enclosed inititializer syntax either as an expression as in

  T{create<Args>()...}

or as a variable definition as in:

  T t{create<Args>()...};

was indeed considered. But it was decided to better stay away from list-initialization in the definition of the is_constructible trait, because parentheses-based constructions are the only-known general construction syntax in C++03 and because brace-based constructions do follow several different rules (e.g. they prevent narrowing conversions and may prefer a better matching initializer-list constructor), which are not really compatible with the () syntax. Without wanting to break user-code, most library parts need to refer to the parentheses, though, at least in the generic code parts. Nevertheless, list-initializations are expected to become very popular and it is recommended to consider to provide a corresponding construction trait, e.g. is_list_constructible, in the next revision of the standard.

Proposed resolution

If applied, this proposal will resolve library issue 1260.

  1. Change [meta.type.synop], header <type_traits> synopsis as indicated:

    namespace std {
      ...
      // 20.7.5, type relations:
      template <class T, class U> struct is_same;
      template <class Base, class Derived> struct is_base_of;
      template <class From, class To> struct is_convertible;
      template <class From, class To> struct is_explicitly_convertible;
      ...
    }
    
  2. Change [meta.unary.prop]/6 as indicated:

    Given the following function prototype:

      template <class T>
      typename add_rvalue_reference<T>::type create();
    

    the predicate condition for a template specialization is_constructible<T, Args...> shall be satisfied if and only if the following expression CEvariable definition would be well-formed:

    T t(create<Args>()...);

    [Note: These tokens are never interpreted as a function declaration. — end note]
    • if sizeof...(Args) == 1, the expression:

      static_cast<T>(create<Args>()...)

    • otherwise, the expression:

      T(create<Args>()...)

  3. Change in [meta.unary.prop] as indicated:

    Table 43 — Type property predicates
    Template Condition Preconditions
    ... ... ...
    template <class T, class... Args>
    struct is_nothrow_constructible;
    is_constructible<T, Args...>::value is true
    and the expression CEvariable definition, as defined below, is known
    not to throw any exceptions.
    ...
    ... ... ...
  4. Change in [meta.rel] as indicated:

    Table 45 — Type relationship predicates
    Template Condition Comments
    ... ... ...
    template <class From, class To>
    struct is_explicitly_convertible;
    is_constructible<To, From>::value is_explicitly_convertible is
    a synonym for a two-argument version of
    is_constructible.An implementation may define
    it as a template alias.
    ... ... ...

Acknowledgements

I would like to thank Peter Dimov for pointing out the defect in the is_constructible definition, and Howard Hinnant for his very helpful discussions and comments during reviews of this paper. The generous help of Stephen Adamczyk, Mike Miller, and John Spicer in interpreting core wording is much appreciated. Further thanks to Alberto Ganesh Barbati, Niels Dekker, and Peter Dimov for discussing MoveConstructible requirements in the context of this proposal.