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
Tas 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 neithervolatilenorconst 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
edetermined 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 asconstexprif 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)
gis a value of the result of abindinvocation,- (1.1)
FDis the typedecay_t<F>,- (1.2)
fdis an lvalueof typethat is a target object ofFDconstructed fromstd::forward<F>(f),g([func.def]) of typeFDdirect-non-list-initialized withstd::forward<F>(f),- (1.3)
Tiis theith type in the template parameter packBoundArgs,- (1.4)
TDiis the typedecay_t<Ti>,- (1.5)
tiis theith argument in the function parameter packbound_args,- (1.6)
tdiisan lvalue of typea bound argument entity ofTDiconstructed fromstd::forward<Ti>(ti),g([func.def]) of typeTDidirect-non-list-initialized withstd::forward<Ti>(ti),- (1.7)
Ujis thejth deduced type of theUnBoundArgs&&...parameter of the argument forwarding call wrapper, and- (1.8)
ujis thejth argument associated withUj.2
RequiresMandates:is_constructible_v<FD, F>shall beistrue. For eachTiinBoundArgs,is_constructible_v<TDi, Ti>shall beistrue.2a Expects:
FDand eachTDimeet the Cpp17MoveConstructible and Cpp17Destructible requirements.INVOKE(fd, w1, w2, …, wN)([func.require])shall beis a valid expression for some valuesw1, w2, …, wN, whereNhas the valuesizeof...(bound_args).The cv-qualifiers cv of the call wrapperg, as specified below, shall be neithervolatilenorconst volatile.3 Returns: An argument forwarding call wrapper
g([func.require]). A program that attempts to invoke a volatile-qualifiedgis ill-formed. Whengis 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 argumentvfdand of the bound argumentsv1, v2, …, vNare 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 ofFDor of any of the typesTDithrows an exception.4 Throws:
Nothing unless the construction ofAny exception thrown by the initialization of the state entities offdor of one of the valuestdithrows an exception.g.5
Remarks: The return type shall satisfy the Cpp17MoveConstructible requirements. If all ofFDandTDisatisfy the Cpp17CopyConstructible requirements, then the return type shall satisfy the Cpp17CopyConstructible requirements. [Note: This implies that all ofFDandTDiare Cpp17MoveConstructible. —end note]5a [Note: If all of
FDandTDimeet 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 eachTiinBoundArgs,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, whereNhas the valuesizeof...(bound_args). The cv-qualifiers cv of the call wrapperg, as specified below, shall be neithervolatilenorconst 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, …, vNare 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 ofFDor of any of the typesTDithrows an exception.8 Throws: Nothing unless the construction of
fdor of one of the valuestdithrows an exception.9 Remarks: The return type shall satisfy the Cpp17MoveConstructible requirements. If all of
FDandTDisatisfy the Cpp17CopyConstructible requirements, then the return type shall satisfy the Cpp17CopyConstructible requirements. [Note: This implies that all ofFDandTDiare 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, …,vNand their corresponding typesV1,V2, …,VNdepend on the typesTDiderived from the call to bind and the cv-qualifiers cv of the call wrappergas follows:
- (10.1) if
TDiisreference_wrapper<T>, […]- (10.2) if the value of
is_bind_expression_v<TDi>istrue, the argument isand its typetdistatic_cast<TDi cv &>(tdi)(std::forward<Uj>(uj)…)Viisinvoke_result_t<TDi cv &, Uj…>&&;- (10.3) if the value
jof […]- (10.4) otherwise, […]
11 The value of the target argument
vfdisfdand its corresponding typeVfdisFD 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
fnsuch that the expressionwith call patternfn(t, a2, …, aN)is equivalent toINVOKE(pm, t, a2, …, aN)([func.require]).invoke(pmd, call_args...), wherepmdis the target object offnof typeR T::*direct-non-list-initialized withpm, andcall_argsis 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