2833. Library needs to specify what it means when it declares a function constexpr

Section: 22.6.3.2 [variant.ctor] Status: Open Submitter: Richard Smith Opened: 2016-11-28 Last modified: 2024-06-24

Priority: 2

View other active issues in [variant.ctor].

View all other issues in [variant.ctor].

View all issues with Open status.

Discussion:

The library has lots of functions declared constexpr, but it's not clear what that means. The constexpr keyword implies that there needs to be some invocation of the function, for some set of template arguments and function arguments, that is valid in a constant expression (otherwise the program would be ill-formed, with no diagnostic required), along with a few side conditions. I suspect the library intends to require something a lot stronger than that from implementations (something along the lines of "all calls that could reasonably be constant subexpressions are in fact constant subexpressions, unless otherwise stated").

[variant.ctor]/1 contains this, which should also be fixed:

"This function shall be constexpr if and only if the value-initialization of the alternative type T0 would satisfy the requirements for a constexpr function."

This is the wrong constraint: instead of constraining whether the function is constexpr, we should constrain whether a call to it is a constant subexpression.

Daniel:

This is has some considerable overlap with LWG 2289 but is phrased in a more general way.

[2016-12-16, Issues Telecon]

Priority 2; this is also the general case of 2829.

[2017-02-20, Alisdair comments and suggests concrete wording]

Below is is draft wording I was working on at Issaquah to try to address both issues.

[2017-11 Albuquerque Wednesday issue processing]

Status to Open; really needs a paper.

STL says "What about plus<T>?" plus<int> needs to be usable in a constexpr context, but plus<string> can't be.

[2017-11 Albuquerque Saturday issues processing]

Geoffrey to write a paper resolving this.

[2018-06 Rapperswil Thursday issues processing]

Geoffrey has been unable to write this paper due to time constraints. He wrote up his progress here. Daniel has offered to help someone to write this paper; he's willing to be a co-author.

[2018-08-23 Batavia Issues processing]

Michael Wong to investigate.

Previous resolution from Daniel [SUPERSEDED]:

This wording is relative to N4640.

  1. Modify 16.4.6.7 [constexpr.functions] as indicated:

    17.6.5.6 constexpr functions and constructors [constexpr.functions]

    -1- This International Standard explicitly requires that certain standard library functions are constexpr (9.2.6 [dcl.constexpr]). If the specification for a templated entity requires that it shall be a constexpr templated entity, then that templated entity shall be usable in a constant expression.. An implementation shall notmay declare anyadditional standard library function signature as constexpr except for those where it is explicitly required. Within any header that provides any non-defining declarations of constexpr functions or constructors an implementation shall provide corresponding definitions.

[2020-06-08 Nina Dinka Ranns comments and provides alternative wording]

The revised wording draft also resolves LWG 2289, LWG 2829, and LWG 3215.

Previous resolution [SUPERSEDED]:

This wording is relative to N4861.

  1. 1. Modify 16.4.6.7 [constexpr.functions] as indicated:

    -1- This document explicitly requires that certain standard library functions are constexpr (9.2.6 [dcl.constexpr]). An implementation shall not declare any standard library function signature as constexpr except for those where it is explicitly required. Within any header that provides any non-defining declarations of constexpr functions or constructors an implementation shall provide corresponding definitions.

    -?- Let F denote a standard library function template or member function of a class template. If the specification of F declares it to be constexpr, unless otherwise specified, then F can be used in a constant expression if and only if all the expressions that are evaluated as specified in the description of F's semantics can be used in a constant expression.

  2. 2. - 10. […] // Remainder of Nina's update

[2020-10-02 Jens Maurer improves wording]

Specifically the wording for 16.4.6.7 [constexpr.functions] needs improvement and is updated below.

[2020-10-02 Tim Song comments]

The new wording doesn't cover the following example:

// global scope
int x;
int y;

constexpr int j = (std::swap(x, y), 0); // error

[2020-10-04 Jens Maurer comments]

Yes, we're still lacking text for that (and maybe Nina's old text helps for that).

[2020-12-14; Jiang An comments]

The item "constexpr functions" is also used in 23.2.2 [container.requirements.general]/14 and 24.3.1 [iterator.requirements.general]/16, and such usage should also be modified by this issue here.

[St. Louis 2024-06-24; Re-confirmed Tim's previous observation, new P/R needed. Jens says there are two ways that swap could work, and the library doesn't actually say how it does what it does, so it's not possible for a reader to know whether they can expect it to be usable in a constant expression. ]

Proposed resolution:

This wording is relative to N4861.

  1. Modify 16.4.6.7 [constexpr.functions] as indicated:

    -1- This document explicitly requires that certain standard library functions are constexpr (9.2.6 [dcl.constexpr]). An implementation shall not declare any standard library function signature as constexpr except for those where it is explicitly required. Within any header that provides any non-defining declarations of constexpr functions or constructors an implementation shall provide corresponding definitions.

    -?- Let F denote a standard library function template or member function of a class template declared constexpr. Unless otherwise specified, a function call expression (7.6.1.3 [expr.call]) whose postfix-expression names F is a constant expression if all of the argument subexpressions are constant expressions.

  2. Modify 22.3.2 [pairs.pair] as indicated:

    -2- The defaulted move and copy constructors, respectively, of pair is a constexpr functioncan be used in a constant expression if and only if all required element-wise initializations for move and copy, respectively, would satisfy the requirements for a constexpr functioncan be used in a constant expression.

  3. Modify 22.4.4.2 [tuple.cnstr] as indicated:

    -3- The defaulted move and copy constructors, respectively, of tuple is a constexpr functioncan be used in a constant expression if and only if all required element-wise initializations for move and copy, respectively, would satisfy the requirements for a constexpr functioncan be used in a constant expression. The defaulted move and copy constructors of tuple<> are constexpr functionscan be used in a constant expression.

  4. Modify 22.5.3.2 [optional.ctor] as indicated:

    constexpr optional() noexcept;
    constexpr optional(nullopt_t) noexcept;
    

    -1- […]

    -2- Remarks: No contained value is initialized. For every object type T these constructors are constexpr constructors (9.2.6 [dcl.constexpr]).

    […]
    template<class... Args> constexpr explicit optional(in_place_t, Args&&... args);
    

    -12- […]

    -13- […]

    -14- […]

    -15- […]

    -16- Remarks: If T's constructor selected for the initialization is a constexpr constructor, this constructor is a constexpr constructor.

    template<class U, class... Args>
      constexpr explicit optional(in_place_t, initializer_list<U> il, Args&&... args);
    

    -17- […]

    -18- […]

    -19- […]

    -20- […]

    -21- Remarks: If T's constructor selected for the initialization is a constexpr constructor, this constructor is a constexpr constructor.

    template<class U = T> constexpr explicit(see below) optional(U&& v);
    

    -22- […]

    -23- […]

    -24- […]

    -25- […]

    -26- Remarks: If T's constructor selected for the initialization is a constexpr constructor, this constructor is a constexpr constructor. The expression inside explicit is equivalent to:

    !is_convertible_v<U, T>
    
  5. Modify 22.5.3.7 [optional.observe] as indicated:

    constexpr const T* operator->() const;
    constexpr T* operator->();
    

    -1- […]

    -2- […]

    -3- […]

    -4- Remarks: These functions are constexpr functions.

    constexpr const T& operator*() const&;
    constexpr T& operator*() &;
    

    -5- […]

    -6- […]

    -7- […]

    -8- Remarks: These functions are constexpr functions.

    […]
    constexpr explicit operator bool() const noexcept;
    

    -11- Returns: true if and only if *this contains a value.

    -12- Remarks: This function is a constexpr function.

    constexpr bool has_value() const noexcept;
    

    -13- Returns: true if and only if *this contains a value.

    -14- Remarks: This function is a constexpr function.

  6. Modify 22.5.6 [optional.relops] as indicated:

    template<class T, class U> constexpr bool operator==(const optional<T>& x, const optional<U>& y);
    

    -1- […]

    -2- […]

    -3- Remarks: Specializations of this function template for which *x == *y is a core constant expression are constexpr functions.

    template<class T, class U> constexpr bool operator!=(const optional<T>& x, const optional<U>& y);
    

    -4- […]

    -5- […]

    -6- Remarks: Specializations of this function template for which *x != *y is a core constant expression are constexpr functions.

    template<class T, class U> constexpr bool operator<(const optional<T>& x, const optional<U>& y);
    

    -7- […]

    -8- […]

    -9- Remarks: Specializations of this function template for which *x < *y is a core constant expression are constexpr functions.

    template<class T, class U> constexpr bool operator>(const optional<T>& x, const optional<U>& y);
    

    -10- […]

    -11- […]

    -12- Remarks: Specializations of this function template for which *x > *y is a core constant expression are constexpr functions.

    template<class T, class U> constexpr bool operator<=(const optional<T>& x, const optional<U>& y);
    

    -13- […]

    -14- […]

    -15- Remarks: Specializations of this function template for which *x <= *y is a core constant expression are constexpr functions.

    template<class T, class U> constexpr bool operator>=(const optional<T>& x, const optional<U>& y);
    

    -16- […]

    -17- […]

    -18- Remarks: Specializations of this function template for which *x >= *y is a core constant expression are constexpr functions.

    template<class T, three_way_comparable_with<T> U>
      constexpr compare_three_way_result_t<T,U>
        operator<=>(const optional<T>& x, const optional<U>& y);
    

    -19- Returns: If x && y, *x <=> *y; otherwise bool(x) <=> bool(y).

    -20- Remarks: Specializations of this function template for which *x <=> *y is a core constant expression are constexpr functions.

  7. Modify 22.6.3.2 [variant.ctor] as indicated:

    constexpr variant() noexcept(see below);
    

    -1- […]

    -2- […]

    -3- […]

    -4- […]

    -5- […]

    -6- Remarks: This function is constexpr if and only if the value-initialization of the alternative type T0 would satisfy the requirements for a constexpr function. The expression inside noexcept is equivalent to is_nothrow_default_constructible_v<T0>. [Note: See also class monostate. — end note]

    […]
    template<class T> constexpr variant(T&& t) noexcept(see below);
    

    -14- […]

    […]

    -19- Remarks: The expression inside noexcept is equivalent to is_nothrow_constructible_v<Tj, T>. If Tj's selected constructor is a constexpr constructor, this constructor is a constexpr constructor.

    template<class T, class... Args> constexpr explicit variant(in_place_type_t<T>, Args&&... args);
    

    -20- […]

    […]

    -24- Remarks: If T's selected constructor is a constexpr constructor, this constructor is a constexpr constructor.

    template<class T, class U, class... Args>
      constexpr explicit variant(in_place_type_t<T>, initializer_list<U> il, Args&&... args);
    

    -25- […]

    […]

    -29- Remarks: If T's selected constructor is a constexpr constructor, this constructor is a constexpr constructor.

    template<size_t I, class... Args> constexpr explicit variant(in_place_index_t<I>, Args&&... args);
    

    -30- […]

    […]

    -34- Remarks: If TI's selected constructor is a constexpr constructor, this constructor is a constexpr constructor.

    template<size_t I, class U, class... Args>
      constexpr explicit variant(in_place_index_t<I>, initializer_list<U> il, Args&&... args);
    

    -35- […]

    […]

    -38- Remarks: If TI's selected constructor is a constexpr constructor, this constructor is a constexpr constructor.

  8. Modify 24.5.4.11 [move.sent.ops] as indicated:

    constexpr move_sentinel();
    

    -1- Effects: Value-initializes last. If is_trivially_default_constructible_v<S> is true, then this constructor is a constexpr constructor.

  9. Modify 22.11.3 [bit.cast] as indicated:

    template<class To, class From>
      constexpr To bit_cast(const From& from) noexcept;
    

    -1- […]

    -3- Remarks: This function is constexprcan be used in a constant expression if and only if To, From, and the types of all subobjects of To and From are types T such that:

    1. (3.1) — is_union_v<T> is false;

    2. (3.2) — is_pointer_v<T> is false;

    3. (3.3) — is_member_pointer_v<T> is false;

    4. (3.4) — is_volatile_v<T> is false; and

    5. (3.5) — T has no non-static data members of reference type.

  10. Modify 30.5 [time.duration] as indicated:

    -5- The defaulted copy constructors of duration shall be a constexpr functioncan be used in a constant expression if and only if the required initialization of the member rep_ for copy and move, respectively, would satisfy the requirements for a constexpr functioncan be used in a constant expression.