This paper proposes allowing usage of virtual inheritance. This will allow in future constexpr-ification of std::ios_base, and streams, removing final limitation for making most of stream formatting constexpr.
This will also remove definition of constexpr-suitable as it will be meaningless and clean all references across draft of the standard (this paper needs to land after P3367: constexpr coroutines).
Last language syntax thing which is disallowed in [dcl.constexpr] which blocks us from making std::stringstream constant evaluatable, which blocks us from making exception types for <chrono> (chrono::nonexistent_local_time and chrono::ambiguous_local_time). Stream formatting <chrono>. Using basic_istream_view in range code for compile-time parsing.
When we remove this limitations, language will only have limit on evaluation properties of code in [expr.const], and not syntactical (with exception of reinterpret_cast, but this is also defined as not constant evaluatable.)
Then keyword constexpr can be seen only as an opt-in into constant evaluatable and can be later removed and replaced with an opt-out attribute (this is not propesed here.)
In progress in clang's fork. Currently structures are represented as APValue object with type field struct and pointer to an array of APValue-s containing nAPValue-s representing base types and kAPValue representing each member subobject.
Lookup for outermost object is already implemented in clang in order to implement virtual function calls. So only changes needed are:
Allow functions to be constexpr if they are within type with virtual bases (in SemaDeclCXX.cpp, note: clang currently disallows not just constructor/destructor but all member functions to be constexpr)
Allow default constructors for types with virtual bases (in SemaType.cpp)
Removing last limitation on constexpr-suitable as defined in [dcl.constexpr] will make it a tautology as every function will now be constexpr-suitable hence this paper contains changes to removal of it across wording.
But this term was used somehow badly to make specify what must be constant evaluatable. It's a subject of LWG issue 2833. Library needs to invent wording specifying something like "implementation must make sure this functionality is constant evaluatable by avoiding constructs disallowed in [expr.const]". As constexpr keyword doesn't mean something must be constant-evaluatable, it's just an opt-in into evaluation for some (or none) code-paths.
I still kept removal of all references to constexpr-suitable in this paper for the project editor's convenience but I guess LWG will do some changes there as they will resolve the issue 2833.
An invocation of a constexpr function in a given context
produces the same result as
an invocation of an equivalent non-constexpr function in the same context
in all respects except that
Note: all following changes removes reference to now non-existing term constexpr-suitable. If it was a requirement for making function marked constexpr, this makes them constexpr unconditionaly. I'm considering these changes editorial and not changing any meaning.
The function call operator or any given operator template specialization
is a constexpr function if either
the corresponding lambda-expression's
parameter-declaration-clause
is followed by constexpr or consteval, or
it is constexpr-suitable ([dcl.constexpr]).
[Example 4: auto monoid =[](auto v){return[=]{return v; }; };
auto add =[](auto m1)constexpr{auto ret = m1();
return[=](auto m2)mutable{auto m1val = m1();
auto plus =[=](auto m2val)mutableconstexpr{return m1val += m2val; };
ret = plus(m2());
return monoid(ret);
};
};
constexprauto zero = monoid(0);
constexprauto one = monoid(1);
static_assert(add(one)(zero)()== one()); // OK// Since two below is not declared constexpr, an evaluation of its constexpr member function call operator// cannot perform an lvalue-to-rvalue conversion on one of its subobjects (that represents its capture)// in a constant expression.auto two = monoid(2);
assert(two()==2); // OK, not a constant expression.static_assert(add(one)(one)()== two()); // error: two() is not a constant expressionstatic_assert(add(one)(one)()== monoid(2)()); // OK — end example]
[Example 5: template<typename T>concept C1 =/* ... */;
template<std::size_t N>concept C2 =/* ... */;
template<typename A, typename B>concept C3 =/* ... */;
auto f =[]<typename T1, C1 T2>requires C2<sizeof(T1)+sizeof(T2)>(T1 a1, T1 b1, T2 a2, auto a3, auto a4)requires C3<decltype(a4), T2>{// T2 is constrained by a type-constraint.// T1 and T2 are constrained by a requires-clause, and// T2 and the type of a4 are constrained by a trailing requires-clause.};
— end example]
An expression E is a core constant expression
unless the evaluation of E, following the rules of the abstract
machine ([intro.execution]), would evaluate one of the following:
A function explicitly defaulted on its first declaration
is implicitly inline ([dcl.inline]),
and is implicitly constexpr ([dcl.constexpr])
if it is constexpr-suitable.
If that user-written default constructor would be ill-formed,
the program is ill-formed.
If that user-written default constructor would be constexpr-suitable ([dcl.constexpr]),
theAn implicitly-defined
default constructor is constexpr.
Before the defaulted default constructor for a class is
implicitly defined,
all the non-user-provided default constructors for its base classes and
its non-static data members are implicitly defined.
[class.base.init] describes the order in which constructors for base
classes and non-static data members are called and
describes how arguments can be specified for the calls to these constructors.
If an implicitly-defined ([dcl.fct.def.default]) constructor would be constexpr-suitable ([dcl.constexpr]),
theAn implicitly-defined
constructor is constexpr.
Before the defaulted copy/move constructor for a class is
implicitly defined,
all non-user-provided copy/move constructors for its
potentially constructed subobjects
are implicitly defined.
Before a
defaulted destructor for a class is implicitly defined, all the non-user-provided
destructors for its base classes and its non-static data members are
implicitly defined.
Constructors and member functions of pair do not throw exceptions unless one of
the element-wise operations specified to be called for that operation
throws an exception.
The defaulted move and copy constructor, respectively, of pair
is a constexpr function if and only if all required element-wise
initializations for move and copy, respectively,
would be constexpr-suitable ([dcl.constexpr]).
The defaulted move and copy constructor, respectively, of
tuple is a constexpr function if and only if all
required element-wise initializations for move and copy, respectively,
would be constexpr-suitable ([dcl.constexpr]).
The defaulted move and copy constructor of tuple<> are
constexpr functions.
The defaulted copy constructor of duration shall be a
constexpr function if and only if the required initialization
of the member rep_ for copy and move, respectively, would
be constexpr-suitable ([dcl.constexpr]).
[Example 1: duration<long, ratio<60>> d0; // holds a count of minutes using a long
duration<longlong, milli> d1; // holds a count of milliseconds using a long long
duration<double, ratio<1, 30>> d2; // holds a count with a tick period of 130 of a second// (30 Hz) using a double — end example]