constexpr
INVOKE
Document #: | P1065R2 |
Date: | 2019-07-16 |
Project: | Programming Language C++ LWG |
Reply-to: |
Tomasz Kamiński <tomaszkam@gmail.com> Barry Revzin <barry.revzin@gmail.com> |
Since [P1065R0], just wording changes to correctly describe what it means for things bind
to be constexpr
and also including bind_front()
.
Currently, one of the most important utility functions in the standard libary, std::invoke()
, is not constexpr
. Even though std::apply()
and std::visit()
, both of which rely on INVOKE
, are both constexpr
. The standard library thus finds itself in an odd state where std::invoke()
is and is not constexpr
.
The reason that std::invoke()
is not constexpr
has some interesting history associated with it. But at this point, it is simply history, and there is no further blocker to making this change. This proposal resolves [LWG2894] but also goes one step further and addresses various other INVOKE
-related machinery.
Our tale beings in April, 2015 with [llvm23141], which presented this code which broke in clang in C++14 (but had compiled in C++11 mode) due to the introduction of a constexpr __invoke
(which ended up breaking range-v3):
#include <functional>
#include <type_traits>
struct Fun
{
template<typename T, typename U>
void operator()(T && t, U && u) const
{
static_assert(std::is_same<U, int &>::value, "");
}
};
int main()
{
std::bind(Fun{}, std::placeholders::_1, 42)("hello");
}
as well as the similar [llvm23135], which was about this program:
template<typename T>
int f(T x)
{
return x.get();
}
template<typename T>
constexpr int g(T x)
{
return x.get();
}
int main() {
// O.K. The body of `f' is not required.
decltype(f(0)) a;
// Seems to instantiate the body of `g'
// and results in an error.
decltype(g(0)) b;
return 0;
}
In both cases the fundamental issue was eager instantiation of the body, which doesn’t actually seem necessary to determine the results here. In neither example is the return type deduced.
These are incarnations of [CWG1581], which dealt with the question of when, exactly, are constexpr
functions defined. In the broken programs above, the constexpr
functions (the non-const
call operator of the binder object being returned in the first case and g()
in the second) were eagerly instantiated, triggering hard compile errors, in cases where the program ultimately would not have required their instantiation.
Thankfully, this difficult problem has been resolved by the adoption of [P0859R0] in Albuquerque, 2017. As a result, both of the above programs are valid.
This issue was the blocker for having a constexpr std::invoke()
due to this eager instantiation issue - which no longer exists.
This proposal adds constexpr
to the following INVOKE
-related machinery: invoke()
, reference_wrapper<T>
, not_fn()
, bind()
, bind_front()
, and mem_fn()
. The remaining non-constexpr
elements of the library that are INVOKE
-adjacent are function<Sig>
, packaged_task<Sig>
, async()
, thread
, and call_once()
.
This proposal resolves [LWG2894], [LWG2957], and [LWG3023]. The last is addressed by guaranteeing that call wrappers that are produced by not_fn()
and bind()
have the same type if their state entities have the same type (note that this guarantee does not imply any restriction on implementors). Thus the types of f1
, f2
, f3
, and f4
in the following example are now guaranteed to be the same:
auto func = [](std::string) {};
std::string s("foo");
auto f1 = std::bind(func, s);
auto f2 = std::bind(std::as_const(func), std::as_const(s));
auto f3 = std::bind(func, std::string("bar"));
auto f4 = std::bind(std::move(func), std::move(s));
The wording uses the phrase “shall be constexpr functions” in a couple places. We don’t seem to have a way to say that in Library, see also [LWG2833] and [LWG2289].
In 17.3.1 [support.limits.general], add a feature test macro:
Macro Name | Value | Header(s) |
---|---|---|
__cpp_lib_constexpr_invoke
|
??????L
|
<functional>
|
Add constexpr
to several places in the synopsis in 20.14.1 [functional.syn]
namespace std { // [func.invoke], invoke template<class F, class... Args> - invoke_result_t<F, Args...> invoke(F&& f, Args&&... args) + constexpr invoke_result_t<F, Args...> invoke(F&& f, Args&&... args) noexcept(is_nothrow_invocable_v<F, Args...>); // [refwrap], reference_wrapper template<class T> class reference_wrapper; - template<class T> reference_wrapper<T> ref(T&) noexcept; - template<class T> reference_wrapper<const T> cref(const T&) noexcept; + template<class T> constexpr reference_wrapper<T> ref(T&) noexcept; + template<class T> constexpr reference_wrapper<const T> cref(const T&) noexcept; template<class T> void ref(const T&&) = delete; template<class T> void cref(const T&&) = delete; - template<class T> reference_wrapper<T> ref(reference_wrapper<T>) noexcept; - template<class T> reference_wrapper<const T> cref(reference_wrapper<T>); + template<class T> constexpr reference_wrapper<T> ref(reference_wrapper<T>) noexcept; + template<class T> constexpr reference_wrapper<const T> cref(reference_wrapper<T>) noexcept; // [arithmetic.operations], arithmetic operations // ... // [comparisons], comparisons // ... // [logical.operations], logical operations // ... // [bitwise.operations], bitwise operations // ... // [func.identity], identity // ... // [func.not.fn], function template not_fn - template<class F> unspecified not_fn(F&& f); + template<class F> constexpr unspecified not_fn(F&& f); // [func.bind.front], function template bind_front - template<class F, class... Args> unspecified bind_front(F&&, Args&&...); + template<class F, class... Args> constexpr unspecified bind_front(F&&, Args&&...); // [func.bind], bind template<class T> struct is_bind_expression; template<class T> struct is_placeholder; - template<class F, class... BoundArgs> - unspecified bind(F&&, BoundArgs&&...); - template<class R, class F, class... BoundArgs> - unspecified bind(F&&, BoundArgs&&...); + template<class F, class... BoundArgs> + constexpr unspecified bind(F&&, BoundArgs&&...); + template<class R, class F, class... BoundArgs> + constexpr unspecified bind(F&&, BoundArgs&&...); namespace placeholders { // M is the implementation-defined number of placeholders see below _1; see below _2; . . . see below _M; } // [func.memfn], member function adaptors template<class R, class T> - unspecified mem_fn(R T::*) noexcept; + constexpr unspecified mem_fn(R T::*) noexcept; // ... }
The definition of the simple call wrapper (used only for mem_fn
) is changed to be a refinement of perfect forwarding call wrapper, instead of argument forwarding call wrapper. These make the invocation operator conditionally constexpr
and noexcept
. In addition we state explicitly the copy/move constructor/assignment of simple call wrapper is core constant expression. [ Note: The definition of simple call wrapper is still required to guarantee assignability. ]
The requirement of copy/move operation to be defined in terms of state entities is now extended to any argument forwarding call wrapper (as we define them for not_fn
and bind
).
Apply following changes to 20.14.3 [func.require]:
3 Every call wrapper ([func.def]) meets the
isCpp17MoveConstructible and Cpp17Destructible requirements.AAn argument forwarding call wrapper is a call wrapper that can be called with an arbitrary argument list and delivers the arguments to the wrapped callable object as references. This forwarding step delivers rvalue arguments as rvalue references and lvalue arguments as lvalue references.A simple call wrapper is an argument forwarding call wrapper that is Cpp17CopyConstructible and Cpp17CopyAssignable and whose copy constructor, move constructor, copy assignment operator, and move assignment operator do not throw exceptions.[ Note: In a typical implementation, argument forwarding call wrappers have an overloaded function call operator of the form—end note]
4 A perfect forwarding call wrapper is an argument forwarding call wrapper that forwards its state entities to the underlying call expression. This forwarding step delivers a state entity of type
T
as cvT&
when the call is performed on an lvalue of the call wrapper type and as cvT&&
otherwise, where cv represents the cv-qualifiers of the call wrapper and where cv shall be neithervolatile
norconst volatile
.5 A call pattern defines the semantics of invoking a perfect forwarding call wrapper. A postfix call performed on a perfect forwarding call wrapper is expression-equivalent ([defns.expression-equivalent]) to an expression
e
determined from its call pattern cp by replacing all occurrences of the arguments of the call wrapper and its state entities with references as described in the corresponding forwarding steps.a A simple call wrapper is a perfect forwarding call wrapper that meets the Cpp17CopyConstructible and Cpp17CopyAssignable and whose copy constructor, move constructor, and assignment operators are constexpr functions which do not throw exceptions.
6 The copy/move constructor of
a perfectan argument forwarding call wrapper has the same apparent semantics as if memberwise copy/move of its state entities were performed ([class.copy.ctor]). [ Note: This implies that each of the copy/move constructors has the same exception-specification as the corresponding implicit definition and is declared asconstexpr
if the corresponding implicit definition would be considered to be constexpr. —end note ]7
PerfectArgument forwarding call wrappers returned by a given standard library function template have the same type if the types of their corresponding state entities are the same.
Add constexpr
to std::invoke()
in 20.14.4 [func.invoke]
Add constexpr
to std::reference_wrapper<T>
in 20.14.5 [refwrap]
namespace std { template<class T> class reference_wrapper { public: // types using type = T; // construct/copy/destroy template<class U> - reference_wrapper(U&&) noexcept(see below); - reference_wrapper(const reference_wrapper& x) noexcept; + constexpr reference_wrapper(U&&) noexcept(see below); + constexpr reference_wrapper(const reference_wrapper& x) noexcept; // assignment - reference_wrapper& operator=(const reference_wrapper& x) noexcept; + constexpr reference_wrapper& operator=(const reference_wrapper& x) noexcept; // access - operator T& () const noexcept; - T& get() const noexcept; + constexpr operator T& () const noexcept; + constexpr T& get() const noexcept; // invocation template<class... ArgTypes> - invoke_result_t<T&, ArgTypes...> operator()(ArgTypes&&...) const; + constexpr invoke_result_t<T&, ArgTypes...> operator()(ArgTypes&&...) const; }; template<class T> reference_wrapper(T&) -> reference_wrapper<T>; }
And its corresponding subsections, 20.14.5.1 [refwrap.const]
20.14.5.2 [refwrap.assign]
20.14.5.3 [refwrap.access]
20.14.5.4 [refwrap.invoke]
and its helper functions, 20.14.5.5 [refwrap.helpers]
2 Returns:
reference_wrapper<T>(t)
.3 Returns:
ref(t.get())
.4 Returns:
reference_wrapper <const T>(t)
.5 Returns:
cref(t.get())
.
Add constexpr
to std::not_fn()
in 20.14.12 [func.not.fn]:
Add constexpr
to std::bind_front()
in 20.14.13 [func.bind.front]:
Apply the following changes to std::bind()
in 20.14.14.3 [func.bind.bind], merging bind
and bind<R>
:
1 In the text that follows:
- (1.0)
g
is a value of the result of abind
invocation,- (1.1)
FD
is the typedecay_t<F>
,- (1.2)
fd
is an lvalueof typethat is a target object ofFD
constructed fromstd::forward<F>(f)
,g
([func.def]) of typeFD
direct-non-list-initialized withstd::forward<F>(f)
,- (1.3)
Ti
is thei
th type in the template parameter packBoundArgs
,- (1.4)
TDi
is the typedecay_t<Ti>
,- (1.5)
ti
is thei
th argument in the function parameter packbound_args
,- (1.6)
tdi
isan lvalue of typea bound argument entity ofTDi
constructed fromstd::forward<Ti>(ti)
,g
([func.def]) of typeTDi
direct-non-list-initialized withstd::forward<Ti>(ti)
,- (1.7)
Uj
is thej
th deduced type of theUnBoundArgs&&...
parameter of the argument forwarding call wrapper, and- (1.8)
uj
is thej
th argument associated withUj
.2
RequiresMandates:is_constructible_v<FD, F>
shall beistrue
. For eachTi
inBoundArgs
,is_constructible_v<TDi, Ti>
shall beistrue
.2a Expects:
FD
and eachTDi
meet the Cpp17MoveConstructible and Cpp17Destructible requirements.INVOKE(fd, w1, w2, …, wN)
([func.require])shall beis a valid expression for some valuesw1, w2, …, wN
, whereN
has the valuesizeof...(bound_args)
.The cv-qualifiers cv of the call wrapperg
, as specified below, shall be neithervolatile
norconst volatile
.3 Returns: An argument forwarding call wrapper
g
([func.require]). A program that attempts to invoke a volatile-qualifiedg
is ill-formed. Wheng
is not volatile-qualified,The effect ofinvocationg(u1, u2, …, uM)
shall beis expression-equivalent ([defns.expression-equivalent]) toINVOKE(fd, std::forward<V1>(v1), std::forward<V2>(v2), …, std::forward<VN>(vN))
INVOKE(static_cast<Vfd>(vfd), static_cast<V1>(v1), static_cast<V2>(v2), …, static_cast<VN>(vN))
for the first overload, andINVOKE<R>(static_cast<Vfd>(vfd), static_cast<V1>(v1), static_cast<V2>(v2), …, static_cast<VN>(vN))
for the second overload, where the values and types of the target argumentvfd
and of the bound argumentsv1, v2, …, vN
are determined as specified below.The copy constructor and move constructor of the argument forwarding call wrapper shall throw an exception if and only if the corresponding constructor ofFD
or of any of the typesTDi
throws an exception.4 Throws:
Nothing unless the construction ofAny exception thrown by the initialization of the state entities offd
or of one of the valuestdi
throws an exception.g
.5
Remarks: The return type shall satisfy the Cpp17MoveConstructible requirements. If all ofFD
andTDi
satisfy the Cpp17CopyConstructible requirements, then the return type shall satisfy the Cpp17CopyConstructible requirements. [Note: This implies that all ofFD
andTDi
are Cpp17MoveConstructible. —end note]5a [Note: If all of
FD
andTDi
meet the requirements of Cpp17CopyConstructible, then the return type meets the requirements of Cpp17CopyConstructible. -end note]6 Requires:
is_constructible_v<FD, F>
shall betrue
. For eachTi
inBoundArgs
,is_constructible_v<TDi, Ti>
shall be true.INVOKE(fd, w1, w2, …, wN)
([func.require]) shall be a valid expression for some valuesw1, w2, …, wN
, whereN
has the valuesizeof...(bound_args)
. The cv-qualifiers cv of the call wrapperg
, as specified below, shall be neithervolatile
norconst volatile
.7 Returns: An argument forwarding call wrapper g ([func.require]). The effect of
g(u1, u2, …, uM)
shall beINVOKE<R>(fd, std::forward<V1>(v1), std::forward<V2>(v2), …, std::forward<VN>(vN))
where the values and types of the bound argumentsv1, v2, …, vN
are determined as specified below. The copy constructor and move constructor of the argument forwarding call wrapper shall throw an exception if and only if the corresponding constructor ofFD
or of any of the typesTDi
throws an exception.8 Throws: Nothing unless the construction of
fd
or of one of the valuestdi
throws an exception.9 Remarks: The return type shall satisfy the Cpp17MoveConstructible requirements. If all of
FD
andTDi
satisfy the Cpp17CopyConstructible requirements, then the return type shall satisfy the Cpp17CopyConstructible requirements. [Note: This implies that all ofFD
andTDi
are Cpp17MoveConstructible. —end note]
Define vfd
and add reference to the cv-qualifies in 20.14.14.3 [func.bind.bind]/10:
10 The values of the bound arguments
v1
,v2
, …,vN
and their corresponding typesV1
,V2
, …,VN
depend on the typesTDi
derived from the call to bind and the cv-qualifiers cv of the call wrapperg
as follows:
- (10.1) if
TDi
isreference_wrapper<T>
, […]- (10.2) if the value of
is_bind_expression_v<TDi>
istrue
, the argument isand its type
tdistatic_cast<TDi cv &>(tdi)(std::forward<Uj>(uj)…)Vi
isinvoke_result_t<TDi cv &, Uj…>&&
;- (10.3) if the value
j
of […]- (10.4) otherwise, […]
11 The value of the target argument
vfd
isfd
and its corresponding typeVfd
isFD cv &
.
Add constant requirement to the placeholders in 20.14.14.4 [func.bind.place]/1:
1 All placeholder types meet the
shall beCpp17DefaultConstructible and Cpp17CopyConstructible requirements, and their default constructors and copy/move constructors are constexpr functions which doshallnot throw exceptions. It is implementation-defined whether placeholder types meet theareCpp17CopyAssignable requirements, but if so, their. Cpp17CopyAssignable placeholders’copy assignment operators are constexpr functions which doshallnot throw exceptions.
Add constexpr
to std::mem_fn()
in 20.14.15 [func.memfn]
1 Returns: A simple call wrapper
fn
such that the expressionwith call patternfn(t, a2, …, aN)
is equivalent toINVOKE(pm, t, a2, …, aN)
([func.require]).invoke(pmd, call_args...)
, wherepmd
is the target object offn
of typeR T::*
direct-non-list-initialized withpm
, andcall_args
is an argument pack used in a function call expression ([expr.call]) ofpm
.
Thanks to Casey Carter and Agustín Bergé for going over the history of issues surrounding constexpr invoke
and suggesting that this proposal be written. Thanks to Daniel Krügler, Tim Song, and Casey Carter for help on the wording.
[CWG1581] Richard Smith. 2012. When are constexpr member functions defined?
https://wg21.link/cwg1581
[llvm23135] Gonzalo BG. 2015. [C++11/14] Body of constexpr function templates instantiated too eagerly in unevaluated operands.
https://bugs.llvm.org/show_bug.cgi?id=23135
[llvm23141] Eric Niebler. 2015. std::bind
const-qualifying bound arguments captured by value when compiled as C++14.
https://bugs.llvm.org/show_bug.cgi?id=23141
[LWG2289] Daniel Krügler. constexpr guarantees of defaulted functions still insufficient.
https://wg21.link/lwg2289
[LWG2833] Richard Smith. Library needs to specify what it means when it declares a function constexpr.
https://wg21.link/lwg2833
[LWG2894] Great Britain. The function template std::apply() is required to be constexpr, but std::invoke() isn’t.
https://wg21.link/lwg2894
[LWG2957] Tim Song. bind’s specification doesn’t apply the cv-qualification of the call wrapper to the callable object.
https://wg21.link/lwg2957
[LWG3023] Detlef Vollmann. Clarify unspecified call wrappers.
https://wg21.link/lwg3023
[P0859R0] Richard Smith. 2017. Core Issue 1581: When are constexpr member functions defined?
https://wg21.link/p0859r0
[P1065R0] Barry Revzin. 2018. constexpr INVOKE.
https://wg21.link/p1065r0