constexpr INVOKE
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 LWG 2894 but also goes one step further and addresses various other INVOKE-related machinery.
Our tale beings in April, 2015 with llvm bug 23141, 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 llvm bug 23135, 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 CWG 1581, 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 P0859 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(), 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().
The entirety of the wording is the addition of the constexpr keyword in 22 places.
Add constexpr to several places in the synopsis in 19.14.1 [functional.syn]
namespace std { // [func.invoke], invoke template<class F, class... Args>constexprinvoke_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>constexprreference_wrapper<T> ref(T&) noexcept; template<class T>constexprreference_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>constexprreference_wrapper<T> ref(reference_wrapper<T>) noexcept; template<class T>constexprreference_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>constexprunspecifiednot_fn(F&& f); // [func.bind], bind template<class T> struct is_bind_expression; template<class T> struct is_placeholder; template<class F, class... BoundArgs>constexprunspecifiedbind(F&&, BoundArgs&&...); template<class R, class F, class... BoundArgs>constexprunspecifiedbind(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>constexprunspecifiedmem_fn(R T::*) noexcept; // ... }
Add constexpr to the requirements of forwarding call wrapper in 19.4.3 [func.require]
Every call wrapper ([func.def]) shall be Cpp17MoveConstructible. A 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 shall ensure that rvalue arguments are delivered as rvalue references and lvalue arguments are delivered as lvalue references. The defaulted move and copy constructor, respectively, of a forwarding call wrapper shall be a constexpr function if and only if all required element-wise initializations for copy and move, respectively, would satisfy the requirements for a constexpr function. The call operator of a forwarding call wrapper shall be a constexpr function if and only if the underlying call operation would satisfy the requirements of a constexpr function. A simple call wrapper is a 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 forwarding call wrappers have an overloaded function call operator of the form
— end note ]template<class... UnBoundArgs>constexprR operator()(UnBoundArgs&&... unbound_args)cv-qual;
Add constexpr to std::invoke() in 19.14.4 [func.invoke]
template<class F, class... Args>constexprinvoke_result_t<F, Args...> invoke(F&& f, Args&&... args) noexcept(is_nothrow_invocable_v<F, Args...>);
Add constexpr to std::reference_wrapper<T> in 19.14.5 [refwrap]
namespace std { template<class T> class reference_wrapper { public: // types using type = T; // construct/copy/destroy template<class U>constexprreference_wrapper(U&&) noexcept(see below );constexprreference_wrapper(const reference_wrapper& x) noexcept; // assignmentconstexprreference_wrapper& operator=(const reference_wrapper& x) noexcept; // accessconstexproperator T& () const noexcept;constexprT& get() const noexcept; // invocation template<class... ArgTypes>constexprinvoke_result_t<T&, ArgTypes...> operator()(ArgTypes&&...) const; }; template<class T> reference_wrapper(T&) -> reference_wrapper<T>; }
And its corresponding subsections, 19.14.5.1 [refwrap.const]
[...]template<class U>constexprreference_wrapper(U&& u) noexcept(see below );constexprreference_wrapper(const reference_wrapper& x) noexcept;
19.14.5.2 [refwrap.assign]
constexprreference_wrapper& operator=(const reference_wrapper& x) noexcept;
19.14.5.3 [refwrap.access]
[...]constexproperator T& () const noexcept;constexprT& get() const noexcept;
19.14.5.4 [refwrap.invoke]
template<class... ArgTypes>constexprinvoke_result_t<T&, ArgTypes...> operator()(ArgTypes&&... args) const;
and its helper functions, 19.14.5.5 [refwrap.helpers]
1 Returns:template<class T>constexprreference_wrapper<T> ref(T& t) noexcept;reference_wrapper<T>(t).2 Returns:template<class T>constexprreference_wrapper<T> ref(reference_wrapper<T> t) noexcept;ref(t.get()).3 Returns:template<class T>constexprreference_wrapper<const T> cref(const T& t) noexcept;reference_wrapper<const T>(t).4 Returns:template<class T>constexprreference_wrapper<const T> cref(reference_wrapper<T> t) noexcept;cref(t.get()).
Add constexpr to std::not_fn in 19.14.11 [func.not_fn]
1 Effects: Equivalent to:template<class F>constexprunspecifiednot_fn(F&& f);wherereturncall_wrapper(std::forward<F>(f));call_wrapperis an exposition only class defined as follows:classcall_wrapper{ using FD = decay_t<F>; FD fd; explicitconstexpr call_wrapper(F&& f); public:constexpr call_wrapper(call_wrapper&&) = default;constexpr call_wrapper(constcall_wrapper&) = default; template<class... Args>constexprauto operator()(Args&&...) & -> decltype(!declval<invoke_result_t<FD&, Args...>>()); template<class... Args>constexprauto operator()(Args&&...) const& -> decltype(!declval<invoke_result_t<const FD&, Args...>>()); template<class... Args>constexprauto operator()(Args&&...) && -> decltype(!declval<invoke_result_t<FD, Args...>>()); template<class... Args>constexprauto operator()(Args&&...) const&& -> decltype(!declval<invoke_result_t<const FD, Args...>>()); };[...]explicitconstexprcall_wrapper(F&& f);5 Effects: Equivalent to:template<class... Args>constexprauto operator()(Args&&...) & -> decltype(!declval<invoke_result_t<FD&, Args...>>()); template<class... Args>constexprauto operator()(Args&&...) const& -> decltype(!declval<invoke_result_t<const FD&, Args...>>());return !INVOKE(fd, std::forward<Args>(args)...); // see 19.14.36 Effects: Equivalent to:template<class... Args>constexprauto operator()(Args&&...) && -> decltype(!declval<invoke_result_t<FD&, Args...>>()); template<class... Args>constexprauto operator()(Args&&...) const&& -> decltype(!declval<invoke_result_t<const FD&, Args...>>());return !INVOKE(std::move(fd), std::forward<Args>(args)...); // see 19.14.3
Add constexpr to std::bind() in 19.14.12.3 [func.bind.bind]
[...]template<class F, class... BoundArgs>constexpr unspecifiedbind(F&& f, BoundArgs&&... bound_args);template<class R, class F, class... BoundArgs>constexpr unspecifiedbind(F&& f, BoundArgs&&... bound_args);
Add constexpr to std::mem_fn() in 19.14.13 [func.memfn]
template<class R, class T>constexpr unspecifiedmem_fn(R T::* pm) noexcept;
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 Tomasz Kamiński and Tim Song for help on the wording.