Document number: N2976=09-0166

Alisdair Meredith
2009-09-22

N2976 constexpr in the library: take 2

Motivation for paper

As more understanding is gained on the usage of new language features, a number of library issues have been opened requesting better support for static initialization of library types. In some cases this is important (mutex initialization) and in others it is merely convenient (e.g. sentinel iterators.) Rather than simply tackle each existing issue and wait for further issues to be opened, this paper conducts a review of each library clause to determine where additional constexpr constructors are appropriate, and provides full wording for those cases. This has then been cross-referenced to the current issues list to provide guidance on which issues might be considered resolved by this paper.

In a couple of cases, notably pair and tuple, this paper takes a broader look at a number of related concerns in the area of initialization to be sure the issue can be seen as completely resolved, rather than partially addressed.

A second motivation is that a number of Library issues have been reported around the use of constexpr, especially for constructors. Rather than discuss such issues individually as they occur in sequence issue, it will be simpler to collect such issues together and resolve questions on constexpr in a single discussion. As such, this paper addresses many LWG issues but does not directly address many NB comments on the first CD ballot. As such any discussion on this paper should probably be scheduled after NB comments are reviewed, but hopefully not postponed until after the next CD.

This paper aims to resolve each of the following issues:

  • 801 - Open
  • 827 - Open
  • 828 - Review
  • 1019 - Tentatively Read
  • 1116 - New
  • 1117 - New
  • 1122 - New
  • 1129 - New
  • 1171 - New
  • Discussion on the following issue is deferred to a more specialized paper

  • 1156 - New
  • Broad concerns

    Static initialization is generally a good and valuable property when it is 'free'. Notably, static initialization is guaranteed thread-safe (whatever the definition) and provides an initialization appropriate for burning into ROMs. There are many library types where requiring constexpr constructors has little impact on the expected implementation so are whole-heartedly recommended.

    There is another broad category of types where constexpr constructors are supportable, but do constrain the typical implementation. A good example would be vector, or any of the standard containers. It is quite possible to support a constexpr default constructor for empty containers by storing a null pointer to the dynamic storage for the data structure. However, this has an impact on many container members to now explicitly check for the empty case and branch appropriately. While it would be convenient for any implementations already supplied along these lines to support constexpr, it should certainly not be a required implementation technique as there are many trade-offs involved in picking the appropriate data structure and default state, most of which dominate the motivation for static initialization in a few special cases. Thus, the recommendation is that no container type in clause 23 (with the possible exception of array) should require constexpr constructors.

    A similar argument might be made for basic_string or any library type with implied dynamic storage. However, it should be noted that the removal of reference counting support from basic_string and implied transition to 'short string optimization' would provide a default constructor that typically could be declared constexpr. Due to broad similarity with clause 23 containers this paper does not make that recommendation, but it could well be worth discussing further during any paper reivew.

    Benefits of constexpr

    constexpt functions are evaluated at translation time if the are called with constant expressions - typically a literal as an argument, or the result of a evaluating another constexpr function. This is often a more familiar model than using template metaprogramming to achieve the same effect, and should be actively enabled in the library where possible.

    However, the most important aspect of constexpr to the library today is the notion of literal types and constexpr constructors. A type is a literal type all its members are literal types and it has at least one constexpr constructor. The key advantage of literal types is that literal values can be created which must be constant-initialized before main. Constant initialization is stronger guarantee than dynamic initialization, as the value does not not depend on executing runtime code and so is suitable for burning to ROMs. Likewise there are no order-of-initialization concerns and the values can be safely shared in multiple threads without inducing data races.

    Limitations of constexpr

    The constexpr feature contains some valuable support for use in templates, and so generic code, but also some unfortunate limitations.

    If a function is declared constexpr, it is limited to a special form and also places requirements on its argument types. Notably, arguments to a constexpr function must be literal types. In the case of a function template this is relaxed a little, as there is no way to know at template parse time whether or not it will be instantiated with literal types. Rather than create an implied requirement that all arguments are literal (or else the instantiation is ill-formed) the semantics are that if a function template is declared constexpr, the keyword is ignored at instantiation time if the arguments turn out not to be literal types. i.e constexpr is honoured if but only if it can be supported.

    The first serious problem for generic code is that constexpr functions must pass their arguments *by value*. For this reason there are special rules governing constexpr copy constructors. However, in generic code the common idiom is to pass by const reference to avoid a potentially expensive copy of an unknown (until instantiation) type. Broader support for constexpr through the library would probably require a relaxing of the core feature restriction here, and there is a French NB comment exactly that (xref to be supplied.) The LWG may consider sending a message in support of that request to Core, if we believe such a facility is important and something they would use. This paper does not assume any outcome from that process.

    Another restriction on literal types is that while they may perform all their computations at compile time, that alone is not enough to support use as non-type template parameters. That was a part of the original feature design, but did not survive the review process to the CD. There is another French NB comment in this regard (xref to be supplied.) Again, LWG may want to consider how this would affect the standard library and send a message to Core regarding their expected use (or not) of such a feature.

    Finally, constexpr functions are limited to either a function body with a single return statement, or a constructor with an empty function body (that initializes all non-static data members.) This does not raise any undue concerns for this paper.

    Missing applications of constexpr

    One library paper has already been applied as part of the initial constexpr proposal. This paper goes further and reviews all library classes for missed opportunities to support literal values. In many cases this is simply supporting a null value for a type (such as a null shared_ptr) which may seem a small gain, but is still valuable for creating efficient race-free initial states for programs.

    Mis-applications of constexpr

    Certainly not every library type should support a constexpr constructor, either because it would be meaningless, or would be too constraining on the implementation. In this paper we deliberately do not apply constexpr to the following components:

  • basic_string
  • containers
  • insert iterators
  • reverse/move iterator
  • regular expressions
  • exception classes
  • iostreams (other than end iterators)
  • threads
  • futures
  • locks
  • locales
  • function binders
  • Deferred applications of constexpr

    There are a small number of special purpose libraries that remain beyond the scope of this paper. The following may yet benefit from constexpr in the future.

  • bitmask types
  • random number engines
  • random number distributions
  • atomic types
  • The following empty types are typically used for their static properties, so while there is no reason they could not be made into literal types, there is not much motivation either. It might be interesting if at the core language level such empty types (with implicit and trivial special members) implicitly satisfied the constraints for a literal type.

  • type_traits
  • numeric_limits
  • Proposed Solution

    This paper proposed we add constexpr default constructors for the following library classes.

  • pair
  • tuple
  • unique_ptr
  • shared_ptr
  • weak_ptr
  • enable_shared_from_this
  • auto_ptr
  • mutex
  • istream_iterator
  • istreambuf_iterator
  • duration
  • Likewise, we should support constexpr operations on literal values of types that can support them.

  • pair
  • tuple
  • duaration
  • Proposed Wording

    Add constexpr to declaration of following functions and constructors:

    20.2.3 [pairs] / 20.2.3p4 [pairs]

    constexpr pair();
    constexpr pair(const pair&) = default;
    

    20.4.1 [ratio.ratio]

    namespace std {
      template <intmax_t N, intmax_t D = 1>
      class ratio {
      public:
        static constexpr intmax_t num;
        static constexpr intmax_t den;
      };
    }
    

    20.5.2 [tuple.tuple] / 20.5.2.1p4 [tuple.cnstr]

    constexpr tuple();
    constexpr tuple(const tuple&) = default;
    

    20.6.3 [meta.help]

    template <class T, T v>
    struct integral_constant {
      static constexpr T value = v;
      typedef T value_type;
      typedef integral_constant<T,v> type;
      constexpr operator value_type() { return value; }
    };
    

    20.7.11.1.1 [unique.ptr.dltr.dflt] / 20.7.11.1.2 [unique.ptr.dltr.dflt1]

    consexpr default_delete();
    

    20.7.11.2 [unique.ptr.single] / 20.7.11.3 [unique.ptr.runtime]

    consexpr unique_ptr();
    

    20.7.11.2.1 [unique.ptr.single.ctor]

    constexpr unique_ptr();

    1 Requires: D shall be default constructible, and that construction shall not throw an exception. D shall not be a reference type or pointer type (diagnostic required).

    2 Effects: Constructs a unique_ptr which owns nothing.

    3 Postconditions: get() == 0. get_deleter() returns a reference to a default constructedvalue initialized deleter D.

    4 Throws: nothing.

    20.8.10.2 [util.smartptr.shared] / 20.8.10.2.1 [util.smartptr.shared.const]

    consexpr shared_ptr();
    

    20.8.10.3 [util.smartptr.weak] / 20.8.10.3.1 [util.smartptr.weak.const]

    consexpr weak_ptr();
    

    20.8.10.5 [util.smartptr.enab] (2 places)

    consexpr enable_shared_from_this();
    

    20.9 [time]

    Header <chrono> synopsis

    [Draughting note - observe switch to pass-by-value to support constexpr. This would not be necessary if constexpr constructors accepted reference-to-const parameters.]

    // duration arithmetic
    template <class Rep1, class Period1, class Rep2, class Period2>
       typename common_type<duration<Rep1, Period1>, duration<Rep2, Period2>>::type
       constexpr operator+(const duration<Rep1, Period1>& lhs, const duration<Rep2, Period2>& rhs);
    template <class Rep1, class Period1, class Rep2, class Period2>
       typename common_type<duration<Rep1, Period1>, duration<Rep2, Period2>>::type
       constexpr operator-(const duration<Rep1, Period1>& lhs, const duration<Rep2, Period2>& rhs);
    template <class Rep1, class Period, class Rep2>
       duration<typename common_type<Rep1, Rep2>::type, Period>
       constexpr operator*(const duration<Rep1, Period>& d, const Rep2& s);
    template <class Rep1, class Period, class Rep2>
       duration<typename common_type<Rep1, Rep2>::type, Period>
       constexpr operator*(const Rep1& s, const duration<Rep2, Period>& d);
    template <class Rep1, class Period, class Rep2>
       duration<typename common_type<Rep1, Rep2>::type, Period>
       constexpr operator/(const duration<Rep1, Period>& d, const Rep2& s);
    template <class Rep1, class Period1, class Rep2, class Period2>
       typename common_type<Rep1, Rep2>::type
       constexpr operator/(const duration<Rep1, Period1>& lhs, const duration<Rep2, Period2>& rhs);
    
    // duration comparisons
    template <class Rep1, class Period1, class Rep2, class Period2>
       constexpr bool operator==(const duration<Rep1, Period1>& lhs, const duration<Rep2, Period2>& rhs);
    template <class Rep1, class Period1, class Rep2, class Period2>
       constexpr bool operator!=(const duration<Rep1, Period1>& lhs, const duration<Rep2, Period2>& rhs);
    template <class Rep1, class Period1, class Rep2, class Period2>
       constexpr bool operator< (const duration<Rep1, Period1>& lhs, const duration<Rep2, Period2>& rhs);
    template <class Rep1, class Period1, class Rep2, class Period2>
       constexpr bool operator<=(const duration<Rep1, Period1>& lhs, const duration<Rep2, Period2>& rhs);
    template <class Rep1, class Period1, class Rep2, class Period2>
       constexpr bool operator> (const duration<Rep1, Period1>& lhs, const duration<Rep2, Period2>& rhs);
    template <class Rep1, class Period1, class Rep2, class Period2>;
       constexpr bool operator>=(const  duration<Rep1, Period1>& lhs, const duration<Rep2, Period2>& rhs);
    
    // duration_cast
    template <class ToDuration, class Rep, class Period>
       constexpr ToDuration duration_cast(const duration<Rep, Period>& d);
    

    20.9.3 [time.duration]

    template <class Rep, class Period = ratio<1>>
    class duration {
      ....
    public:
      // 20.9.3.1, construct/copy/destroy:
     constexpr duration() = default;
    
     template <class Rep2>
       constexpr explicit duration(const Rep2& r);
     template <class Rep2, class Period2>
       constexpr duration(const duration<Rep2, Period2>& d);
    
      constexpr duration(const duration&) = default;
    
      // 20.9.3.2, observer:
      constexpr rep count() const;
    
      // 20.9.3.3, arithmetic:
      constexpr duration operator+() const;
      constexpr duration operator-() const;
      ...
    
    };
    

    24.6.1p3 [istream.iterator]

    constexpr istream_iterator();
    istream_iterator(istream_type& s);
    constexpr istream_iterator(const istream_iterator<T,charT,traits,Distance>& x) = default;
    ~istream_iterator() = default;
    

    24.6.1.1p1 [istream.iterator.cons]

    constexpr istream_iterator();
    
    -1- Effects: Constructs the end-of-stream iterator. If T is a literal type, then this constructor shall be a constexpr constructor.

    24.6.1.1p3 [istream.iterator.cons]

    constexpr istream_iterator(const istream_iterator<T,charT,traits,Distance>& x) = default;
    
    -3- Effects: Constructs a copy of x. If T is a literal type, then this constructor shall be a trivial copy constructor.

    24.6.1.1p4 [istream.iterator.cons]

    ~istream_iterator() = default;
    
    -4- Effects: The iterator is destroyed. If T is a literal type, then this destructor shall be a trivial destructor.

    24.6.3 [istreambuf.iterator]

    constexpr istreambuf_iterator() throw();
    constexpr istreambuf_iterator(const istreambuf_iterator&)  throw() = default;
    ~istreambuf_iterator()  throw() = default;
    

    24.6.3p1 [istreambuf.iterator]

    [..] The default constructor istreambuf_iterator() and the constructor istreambuf_iterator(0) both construct an end of stream iterator object suitable for use as an end-of-range. All specializations of istreambuf_iterator shall have a trivial copy constructor, a constexpr default constructor and a trivial destructor.

    30.4.1.1 [thread.mutex.class]

    class mutex {
    public:
      constexpr mutex();
      ...
    

    D.9.1 [auto.ptr] / D.9.1.1 [auto.ptr.cons]

    consexpr auto_ptr();
    explicit auto_ptr(X* p =0) throw();