Skip to content

[libc++] Fix expression-equivalence for mem_fn #111307

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 15, 2024

Conversation

frederick-vs-ja
Copy link
Contributor

Previously, SFINAE constraints and exception specification propagation were missing in the return type of libc++'s std::mem_fn. The requirements on expression-equivalence (or even plain "equivalent" in pre-C++20 specification) in [func.memfn] are actually requiring them.

This PR adds the missed stuffs. Fixes #86043.

Drive-by changes:

  • removing no longer used __invoke_return,
  • updating synopsis comments in several files, and
  • merging several test files for mem_fn into one.

@frederick-vs-ja frederick-vs-ja requested a review from a team as a code owner October 6, 2024 18:15
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Oct 6, 2024
@llvmbot
Copy link
Member

llvmbot commented Oct 6, 2024

@llvm/pr-subscribers-libcxx

Author: A. Jiang (frederick-vs-ja)

Changes

Previously, SFINAE constraints and exception specification propagation were missing in the return type of libc++'s std::mem_fn. The requirements on expression-equivalence (or even plain "equivalent" in pre-C++20 specification) in [func.memfn] are actually requiring them.

This PR adds the missed stuffs. Fixes #86043.

Drive-by changes:

  • removing no longer used __invoke_return,
  • updating synopsis comments in several files, and
  • merging several test files for mem_fn into one.

Patch is 33.85 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/111307.diff

10 Files Affected:

  • (modified) libcxx/include/__functional/mem_fn.h (+2-4)
  • (modified) libcxx/include/__functional/weak_result_type.h (-5)
  • (modified) libcxx/include/functional (+1-1)
  • (added) libcxx/test/std/utilities/function.objects/func.memfn/mem_fn.pass.cpp (+741)
  • (modified) libcxx/test/std/utilities/function.objects/func.memfn/member_data.compile.fail.cpp (+1-1)
  • (removed) libcxx/test/std/utilities/function.objects/func.memfn/member_data.pass.cpp (-51)
  • (removed) libcxx/test/std/utilities/function.objects/func.memfn/member_function.pass.cpp (-87)
  • (removed) libcxx/test/std/utilities/function.objects/func.memfn/member_function_const.pass.cpp (-90)
  • (removed) libcxx/test/std/utilities/function.objects/func.memfn/member_function_const_volatile.pass.cpp (-81)
  • (removed) libcxx/test/std/utilities/function.objects/func.memfn/member_function_volatile.pass.cpp (-81)
diff --git a/libcxx/include/__functional/mem_fn.h b/libcxx/include/__functional/mem_fn.h
index 58dbdf871d747b..f246edb334bb14 100644
--- a/libcxx/include/__functional/mem_fn.h
+++ b/libcxx/include/__functional/mem_fn.h
@@ -36,10 +36,8 @@ class __mem_fn : public __weak_result_type<_Tp> {
 
   // invoke
   template <class... _ArgTypes>
-  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20
-
-  typename __invoke_return<type, _ArgTypes...>::type
-  operator()(_ArgTypes&&... __args) const {
+  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 typename __invoke_of<const _Tp&, _ArgTypes...>::type
+  operator()(_ArgTypes&&... __args) const _NOEXCEPT_(__nothrow_invokable<const _Tp&, _ArgTypes...>::value) {
     return std::__invoke(__f_, std::forward<_ArgTypes>(__args)...);
   }
 };
diff --git a/libcxx/include/__functional/weak_result_type.h b/libcxx/include/__functional/weak_result_type.h
index 793775a2903e6f..233d86009a2017 100644
--- a/libcxx/include/__functional/weak_result_type.h
+++ b/libcxx/include/__functional/weak_result_type.h
@@ -221,11 +221,6 @@ struct __weak_result_type<_Rp (_Cp::*)(_A1, _A2, _A3...) const volatile> {
 #endif
 };
 
-template <class _Tp, class... _Args>
-struct __invoke_return {
-  typedef decltype(std::__invoke(std::declval<_Tp>(), std::declval<_Args>()...)) type;
-};
-
 _LIBCPP_END_NAMESPACE_STD
 
 #endif // _LIBCPP___FUNCTIONAL_WEAK_RESULT_TYPE_H
diff --git a/libcxx/include/functional b/libcxx/include/functional
index 3d39f654ddb08a..489d82b2d43ab9 100644
--- a/libcxx/include/functional
+++ b/libcxx/include/functional
@@ -395,7 +395,7 @@ const_mem_fun_ref_t<S,T>    mem_fun_ref(S (T::*f)() const);
 template <class S, class T, class A>
 const_mem_fun1_ref_t<S,T,A> mem_fun_ref(S (T::*f)(A) const);                    // deprecated in C++11, removed in C++17
 
-template<class R, class T> constexpr unspecified mem_fn(R T::*);                // constexpr in C++20
+template<class R, class T> constexpr unspecified mem_fn(R T::*) noexcept;       // constexpr in C++20
 
 class bad_function_call
     : public exception
diff --git a/libcxx/test/std/utilities/function.objects/func.memfn/mem_fn.pass.cpp b/libcxx/test/std/utilities/function.objects/func.memfn/mem_fn.pass.cpp
new file mode 100644
index 00000000000000..a5d2eae6ff11ca
--- /dev/null
+++ b/libcxx/test/std/utilities/function.objects/func.memfn/mem_fn.pass.cpp
@@ -0,0 +1,741 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// <functional>
+
+// template<class R, class T> constexpr unspecified mem_fn(R T::*) noexcept;       // constexpr in C++20
+
+#include <functional>
+#include <cassert>
+#include <utility>
+#include <type_traits>
+
+#include "test_macros.h"
+
+struct A {
+  double data_;
+
+  TEST_CONSTEXPR_CXX14 char test0() { return 'a'; }
+  TEST_CONSTEXPR_CXX14 char test1(int) { return 'b'; }
+  TEST_CONSTEXPR_CXX14 char test2(int, double) { return 'c'; }
+
+  TEST_CONSTEXPR_CXX14 char test0_nothrow() TEST_NOEXCEPT { return 'd'; }
+  TEST_CONSTEXPR_CXX14 char test1_nothrow(int) TEST_NOEXCEPT { return 'e'; }
+  TEST_CONSTEXPR_CXX14 char test2_nothrow(int, double) TEST_NOEXCEPT { return 'f'; }
+
+  TEST_CONSTEXPR char test_c0() const { return 'a'; }
+  TEST_CONSTEXPR char test_c1(int) const { return 'b'; }
+  TEST_CONSTEXPR char test_c2(int, double) const { return 'c'; }
+
+  TEST_CONSTEXPR char test_c0_nothrow() const TEST_NOEXCEPT { return 'd'; }
+  TEST_CONSTEXPR char test_c1_nothrow(int) const TEST_NOEXCEPT { return 'e'; }
+  TEST_CONSTEXPR char test_c2_nothrow(int, double) const TEST_NOEXCEPT { return 'f'; }
+
+  char test_v0() volatile { return 'a'; }
+  char test_v1(int) volatile { return 'b'; }
+  char test_v2(int, double) volatile { return 'c'; }
+
+  char test_v0_nothrow() volatile TEST_NOEXCEPT { return 'd'; }
+  char test_v1_nothrow(int) volatile TEST_NOEXCEPT { return 'e'; }
+  char test_v2_nothrow(int, double) volatile TEST_NOEXCEPT { return 'f'; }
+
+  char test_cv0() const volatile { return 'a'; }
+  char test_cv1(int) const volatile { return 'b'; }
+  char test_cv2(int, double) const volatile { return 'c'; }
+
+  char test_cv0_nothrow() const volatile TEST_NOEXCEPT { return 'd'; }
+  char test_cv1_nothrow(int) const volatile TEST_NOEXCEPT { return 'e'; }
+  char test_cv2_nothrow(int, double) const volatile TEST_NOEXCEPT { return 'f'; }
+};
+
+template <class F>
+TEST_CONSTEXPR_CXX20 bool test_data(F f) {
+  A a  = {0.0};
+  f(a) = 5;
+  assert(a.data_ == 5);
+  A* ap = &a;
+  f(ap) = 6;
+  assert(a.data_ == 6);
+  const A* cap = ap;
+  assert(f(cap) == f(ap));
+  const F& cf = f;
+  assert(cf(ap) == f(ap));
+
+#if TEST_STD_VER >= 11
+  static_assert(noexcept(f(a)), "");
+  static_assert(noexcept(f(ap)), "");
+  static_assert(noexcept(f(cap)), "");
+  static_assert(noexcept(cf(ap)), "");
+#endif
+
+  return true;
+}
+
+template <class F>
+TEST_CONSTEXPR_CXX20 bool test_fun0(F f) {
+  A a = {};
+  assert(f(a) == 'a');
+  A* ap = &a;
+  assert(f(ap) == 'a');
+  const F& cf = f;
+  assert(cf(ap) == 'a');
+
+#if TEST_STD_VER >= 17
+  static_assert(!noexcept(f(a)), "");
+  static_assert(!noexcept(f(ap)), "");
+  static_assert(!noexcept(cf(ap)), "");
+#endif
+
+  return true;
+}
+
+template <class F>
+TEST_CONSTEXPR_CXX20 bool test_fun1(F f) {
+  A a = {};
+  assert(f(a, 1) == 'b');
+  A* ap = &a;
+  assert(f(ap, 2) == 'b');
+  const F& cf = f;
+  assert(cf(ap, 2) == 'b');
+
+#if TEST_STD_VER >= 17
+  static_assert(!noexcept(f(a, 0)), "");
+  static_assert(!noexcept(f(ap, 1)), "");
+  static_assert(!noexcept(cf(ap, 2)), "");
+#endif
+
+  return true;
+}
+
+template <class F>
+TEST_CONSTEXPR_CXX20 bool test_fun2(F f) {
+  A a = {};
+  assert(f(a, 1, 2) == 'c');
+  A* ap = &a;
+  assert(f(ap, 2, 3.5) == 'c');
+  const F& cf = f;
+  assert(cf(ap, 2, 3.5) == 'c');
+
+#if TEST_STD_VER >= 17
+  static_assert(!noexcept(f(a, 0, 0.0)), "");
+  static_assert(!noexcept(f(ap, 1, 2)), "");
+  static_assert(!noexcept(cf(ap, 2, 3.5)), "");
+#endif
+
+  return true;
+}
+
+template <class F>
+TEST_CONSTEXPR_CXX20 bool test_noexcept_fun0(F f) {
+  A a = {};
+  assert(f(a) == 'd');
+  A* ap = &a;
+  assert(f(ap) == 'd');
+  const F& cf = f;
+  assert(cf(ap) == 'd');
+
+#if TEST_STD_VER >= 17
+  static_assert(noexcept(f(a)), "");
+  static_assert(noexcept(f(ap)), "");
+  static_assert(noexcept(cf(ap)), "");
+#endif
+
+  return true;
+}
+
+template <class F>
+TEST_CONSTEXPR_CXX20 bool test_noexcept_fun1(F f) {
+  A a = {};
+  assert(f(a, 1) == 'e');
+  A* ap = &a;
+  assert(f(ap, 2) == 'e');
+  const F& cf = f;
+  assert(cf(ap, 2) == 'e');
+
+#if TEST_STD_VER >= 17
+  static_assert(noexcept(f(a, 0)), "");
+  static_assert(noexcept(f(ap, 1)), "");
+  static_assert(noexcept(cf(ap, 2)), "");
+#endif
+
+  return true;
+}
+
+template <class F>
+TEST_CONSTEXPR_CXX20 bool test_noexcept_fun2(F f) {
+  A a = {};
+  assert(f(a, 1, 2) == 'f');
+  A* ap = &a;
+  assert(f(ap, 2, 3.5) == 'f');
+  const F& cf = f;
+  assert(cf(ap, 2, 3.5) == 'f');
+
+#if TEST_STD_VER >= 17
+  static_assert(noexcept(f(a, 0, 0.0)), "");
+  static_assert(noexcept(f(ap, 1, 2)), "");
+  static_assert(noexcept(cf(ap, 2, 3.5)), "");
+#endif
+
+  return true;
+}
+
+template <class F>
+TEST_CONSTEXPR_CXX20 bool test_const_fun0(F f) {
+  A a = {};
+  assert(f(a) == 'a');
+  A* ap = &a;
+  assert(f(ap) == 'a');
+  const A* cap = &a;
+  assert(f(cap) == 'a');
+  const F& cf = f;
+  assert(cf(ap) == 'a');
+
+#if TEST_STD_VER >= 17
+  static_assert(!noexcept(f(a)), "");
+  static_assert(!noexcept(f(ap)), "");
+  static_assert(!noexcept(f(cap)), "");
+  static_assert(!noexcept(cf(ap)), "");
+#endif
+
+  return true;
+}
+
+template <class F>
+TEST_CONSTEXPR_CXX20 bool test_const_fun1(F f) {
+  A a = {};
+  assert(f(a, 1) == 'b');
+  A* ap = &a;
+  assert(f(ap, 2) == 'b');
+  const A* cap = &a;
+  assert(f(cap, 2) == 'b');
+  const F& cf = f;
+  assert(cf(ap, 2) == 'b');
+
+#if TEST_STD_VER >= 17
+  static_assert(!noexcept(f(a, 0)), "");
+  static_assert(!noexcept(f(ap, 1)), "");
+  static_assert(!noexcept(f(cap, 2)), "");
+  static_assert(!noexcept(cf(ap, 3)), "");
+#endif
+
+  return true;
+}
+
+template <class F>
+TEST_CONSTEXPR_CXX20 bool test_const_fun2(F f) {
+  A a = {};
+  assert(f(a, 1, 2) == 'c');
+  A* ap = &a;
+  assert(f(ap, 2, 3.5) == 'c');
+  const A* cap = &a;
+  assert(f(cap, 2, 3.5) == 'c');
+  const F& cf = f;
+  assert(cf(ap, 2, 3.5) == 'c');
+
+#if TEST_STD_VER >= 17
+  static_assert(!noexcept(f(a, 0, 0.0)), "");
+  static_assert(!noexcept(f(ap, 1, 2)), "");
+  static_assert(!noexcept(f(cap, 2, 3.5)), "");
+  static_assert(!noexcept(cf(ap, 3, 17.29)), "");
+#endif
+
+  return true;
+}
+
+template <class F>
+TEST_CONSTEXPR_CXX20 bool test_const_noexcept_fun0(F f) {
+  A a = {};
+  assert(f(a) == 'd');
+  A* ap = &a;
+  assert(f(ap) == 'd');
+  const A* cap = &a;
+  assert(f(cap) == 'd');
+  const F& cf = f;
+  assert(cf(ap) == 'd');
+
+#if TEST_STD_VER >= 17
+  static_assert(noexcept(f(a)), "");
+  static_assert(noexcept(f(ap)), "");
+  static_assert(noexcept(f(cap)), "");
+  static_assert(noexcept(cf(ap)), "");
+#endif
+
+  return true;
+}
+
+template <class F>
+TEST_CONSTEXPR_CXX20 bool test_const_noexcept_fun1(F f) {
+  A a = {};
+  assert(f(a, 1) == 'e');
+  A* ap = &a;
+  assert(f(ap, 2) == 'e');
+  const A* cap = &a;
+  assert(f(cap, 2) == 'e');
+  const F& cf = f;
+  assert(cf(ap, 2) == 'e');
+
+#if TEST_STD_VER >= 17
+  static_assert(noexcept(f(a, 0)), "");
+  static_assert(noexcept(f(ap, 1)), "");
+  static_assert(noexcept(f(cap, 2)), "");
+  static_assert(noexcept(cf(ap, 3)), "");
+#endif
+
+  return true;
+}
+
+template <class F>
+TEST_CONSTEXPR_CXX20 bool test_const_noexcept_fun2(F f) {
+  A a = {};
+  assert(f(a, 1, 2) == 'f');
+  A* ap = &a;
+  assert(f(ap, 2, 3.5) == 'f');
+  const A* cap = &a;
+  assert(f(cap, 2, 3.5) == 'f');
+  const F& cf = f;
+  assert(cf(ap, 2, 3.5) == 'f');
+
+#if TEST_STD_VER >= 17
+  static_assert(noexcept(f(a, 0, 0.0)), "");
+  static_assert(noexcept(f(ap, 1, 2)), "");
+  static_assert(noexcept(f(cap, 2, 3.5)), "");
+  static_assert(noexcept(cf(ap, 3, 17.29)), "");
+#endif
+
+  return true;
+}
+
+template <class F>
+void test_volatile_fun0(F f) {
+  A a = {};
+  assert(f(a) == 'a');
+  A* ap = &a;
+  assert(f(ap) == 'a');
+  volatile A* cap = &a;
+  assert(f(cap) == 'a');
+  const F& cf = f;
+  assert(cf(ap) == 'a');
+
+#if TEST_STD_VER >= 17
+  static_assert(!noexcept(f(a)), "");
+  static_assert(!noexcept(f(ap)), "");
+  static_assert(!noexcept(f(cap)), "");
+  static_assert(!noexcept(cf(ap)), "");
+#endif
+}
+
+template <class F>
+void test_volatile_fun1(F f) {
+  A a = {};
+  assert(f(a, 1) == 'b');
+  A* ap = &a;
+  assert(f(ap, 2) == 'b');
+  volatile A* cap = &a;
+  assert(f(cap, 2) == 'b');
+  const F& cf = f;
+  assert(cf(ap, 2) == 'b');
+
+#if TEST_STD_VER >= 17
+  static_assert(!noexcept(f(a, 0)), "");
+  static_assert(!noexcept(f(ap, 1)), "");
+  static_assert(!noexcept(f(cap, 2)), "");
+  static_assert(!noexcept(cf(ap, 3)), "");
+#endif
+}
+
+template <class F>
+void test_volatile_fun2(F f) {
+  A a = {};
+  assert(f(a, 1, 2) == 'c');
+  A* ap = &a;
+  assert(f(ap, 2, 3.5) == 'c');
+  volatile A* cap = &a;
+  assert(f(cap, 2, 3.5) == 'c');
+  const F& cf = f;
+  assert(cf(ap, 2, 3.5) == 'c');
+
+#if TEST_STD_VER >= 17
+  static_assert(!noexcept(f(a, 0, 0.0)), "");
+  static_assert(!noexcept(f(ap, 1, 2)), "");
+  static_assert(!noexcept(f(cap, 2, 3.5)), "");
+  static_assert(!noexcept(cf(ap, 3, 17.29)), "");
+#endif
+}
+
+template <class F>
+void test_volatile_noexcept_fun0(F f) {
+  A a = {};
+  assert(f(a) == 'd');
+  A* ap = &a;
+  assert(f(ap) == 'd');
+  volatile A* cap = &a;
+  assert(f(cap) == 'd');
+  const F& cf = f;
+  assert(cf(ap) == 'd');
+
+#if TEST_STD_VER >= 17
+  static_assert(noexcept(f(a)), "");
+  static_assert(noexcept(f(ap)), "");
+  static_assert(noexcept(f(cap)), "");
+  static_assert(noexcept(cf(ap)), "");
+#endif
+}
+
+template <class F>
+void test_volatile_noexcept_fun1(F f) {
+  A a = {};
+  assert(f(a, 1) == 'e');
+  A* ap = &a;
+  assert(f(ap, 2) == 'e');
+  volatile A* cap = &a;
+  assert(f(cap, 2) == 'e');
+  const F& cf = f;
+  assert(cf(ap, 2) == 'e');
+
+#if TEST_STD_VER >= 17
+  static_assert(noexcept(f(a, 0)), "");
+  static_assert(noexcept(f(ap, 1)), "");
+  static_assert(noexcept(f(cap, 2)), "");
+  static_assert(noexcept(cf(ap, 3)), "");
+#endif
+}
+
+template <class F>
+void test_volatile_noexcept_fun2(F f) {
+  A a = {};
+  assert(f(a, 1, 2) == 'f');
+  A* ap = &a;
+  assert(f(ap, 2, 3.5) == 'f');
+  volatile A* cap = &a;
+  assert(f(cap, 2, 3.5) == 'f');
+  const F& cf = f;
+  assert(cf(ap, 2, 3.5) == 'f');
+
+#if TEST_STD_VER >= 17
+  static_assert(noexcept(f(a, 0, 0.0)), "");
+  static_assert(noexcept(f(ap, 1, 2)), "");
+  static_assert(noexcept(f(cap, 2, 3.5)), "");
+  static_assert(noexcept(cf(ap, 3, 17.29)), "");
+#endif
+}
+
+template <class F>
+void test_const_volatile_fun0(F f) {
+  A a = {};
+  assert(f(a) == 'a');
+  A* ap = &a;
+  assert(f(ap) == 'a');
+  const volatile A* cap = &a;
+  assert(f(cap) == 'a');
+  const F& cf = f;
+  assert(cf(ap) == 'a');
+
+#if TEST_STD_VER >= 17
+  static_assert(!noexcept(f(a)), "");
+  static_assert(!noexcept(f(ap)), "");
+  static_assert(!noexcept(f(cap)), "");
+  static_assert(!noexcept(cf(ap)), "");
+#endif
+}
+
+template <class F>
+void test_const_volatile_fun1(F f) {
+  A a = {};
+  assert(f(a, 1) == 'b');
+  A* ap = &a;
+  assert(f(ap, 2) == 'b');
+  const volatile A* cap = &a;
+  assert(f(cap, 2) == 'b');
+  const F& cf = f;
+  assert(cf(ap, 2) == 'b');
+
+#if TEST_STD_VER >= 17
+  static_assert(!noexcept(f(a, 0)), "");
+  static_assert(!noexcept(f(ap, 1)), "");
+  static_assert(!noexcept(f(cap, 2)), "");
+  static_assert(!noexcept(cf(ap, 3)), "");
+#endif
+}
+
+template <class F>
+void test_const_volatile_fun2(F f) {
+  A a = {};
+  assert(f(a, 1, 2) == 'c');
+  A* ap = &a;
+  assert(f(ap, 2, 3.5) == 'c');
+  const volatile A* cap = &a;
+  assert(f(cap, 2, 3.5) == 'c');
+  const F& cf = f;
+  assert(cf(ap, 2, 3.5) == 'c');
+
+#if TEST_STD_VER >= 17
+  static_assert(!noexcept(f(a, 0, 0.0)), "");
+  static_assert(!noexcept(f(ap, 1, 2)), "");
+  static_assert(!noexcept(f(cap, 2, 3.5)), "");
+  static_assert(!noexcept(cf(ap, 3, 17.29)), "");
+#endif
+}
+
+template <class F>
+void test_const_volatile_noexcept_fun0(F f) {
+  A a = {};
+  assert(f(a) == 'd');
+  A* ap = &a;
+  assert(f(ap) == 'd');
+  const volatile A* cap = &a;
+  assert(f(cap) == 'd');
+  const F& cf = f;
+  assert(cf(ap) == 'd');
+
+#if TEST_STD_VER >= 17
+  static_assert(noexcept(f(a)), "");
+  static_assert(noexcept(f(ap)), "");
+  static_assert(noexcept(f(cap)), "");
+  static_assert(noexcept(cf(ap)), "");
+#endif
+}
+
+template <class F>
+void test_const_volatile_noexcept_fun1(F f) {
+  A a = {};
+  assert(f(a, 1) == 'e');
+  A* ap = &a;
+  assert(f(ap, 2) == 'e');
+  const volatile A* cap = &a;
+  assert(f(cap, 2) == 'e');
+  const F& cf = f;
+  assert(cf(ap, 2) == 'e');
+
+#if TEST_STD_VER >= 17
+  static_assert(noexcept(f(a, 0)), "");
+  static_assert(noexcept(f(ap, 1)), "");
+  static_assert(noexcept(f(cap, 2)), "");
+  static_assert(noexcept(cf(ap, 3)), "");
+#endif
+}
+
+template <class F>
+void test_const_volatile_noexcept_fun2(F f) {
+  A a = {};
+  assert(f(a, 1, 2) == 'f');
+  A* ap = &a;
+  assert(f(ap, 2, 3.5) == 'f');
+  const volatile A* cap = &a;
+  assert(f(cap, 2, 3.5) == 'f');
+  const F& cf = f;
+  assert(cf(ap, 2, 3.5) == 'f');
+
+#if TEST_STD_VER >= 17
+  static_assert(noexcept(f(a, 0, 0.0)), "");
+  static_assert(noexcept(f(ap, 1, 2)), "");
+  static_assert(noexcept(f(cap, 2, 3.5)), "");
+  static_assert(noexcept(cf(ap, 3, 17.29)), "");
+#endif
+}
+
+#if TEST_STD_VER >= 11
+template <class V, class Func, class... Args>
+struct is_callable_impl : std::false_type {};
+
+template <class Func, class... Args>
+struct is_callable_impl<decltype((void)std::declval<Func>()(std::declval<Args>()...)), Func, Args...> : std::true_type {
+};
+
+template <class Func, class... Args>
+struct is_callable : is_callable_impl<void, Func, Args...>::type {};
+
+template <class F>
+void test_sfinae_data(F f) {
+  static_assert(is_callable<F, A>::value, "");
+  static_assert(is_callable<F, const A>::value, "");
+  static_assert(is_callable<F, A&>::value, "");
+  static_assert(is_callable<F, const A&>::value, "");
+  static_assert(is_callable<F, A*>::value, "");
+  static_assert(is_callable<F, const A*>::value, "");
+
+  static_assert(!is_callable<F, A, char>::value, "");
+  static_assert(!is_callable<F, const A, char>::value, "");
+  static_assert(!is_callable<F, A&, char>::value, "");
+  static_assert(!is_callable<F, const A&, char>::value, "");
+  static_assert(!is_callable<F, A*, char>::value, "");
+  static_assert(!is_callable<F, const A*, char>::value, "");
+}
+
+template <class F>
+void test_sfinae_fun0(F f) {
+  static_assert(is_callable<F, A>::value, "");
+  static_assert(is_callable<F, A&>::value, "");
+  static_assert(is_callable<F, A*>::value, "");
+
+  static_assert(!is_callable<F, const A>::value, "");
+  static_assert(!is_callable<F, const A&>::value, "");
+  static_assert(!is_callable<F, const A*>::value, "");
+
+  static_assert(!is_callable<F, volatile A>::value, "");
+  static_assert(!is_callable<F, volatile A&>::value, "");
+  static_assert(!is_callable<F, volatile A*>::value, "");
+
+  static_assert(!is_callable<F, const volatile A>::value, "");
+  static_assert(!is_callable<F, const volatile A&>::value, "");
+  static_assert(!is_callable<F, const volatile A*>::value, "");
+
+  static_assert(!is_callable<F, A, int>::value, "");
+  static_assert(!is_callable<F, A&, int>::value, "");
+  static_assert(!is_callable<F, A*, int>::value, "");
+}
+
+template <class F>
+void test_sfinae_fun1(F f) {
+  static_assert(is_callable<F, A, int>::value, "");
+  static_assert(is_callable<F, A&, int>::value, "");
+  static_assert(is_callable<F, A*, int>::value, "");
+
+  static_assert(!is_callable<F, A>::value, "");
+  static_assert(!is_callable<F, A&>::value, "");
+  static_assert(!is_callable<F, A*>::value, "");
+}
+
+template <class F>
+void test_sfinae_const_fun0(F f) {
+  static_assert(is_callable<F, A>::value, "");
+  static_assert(is_callable<F, A&>::value, "");
+  static_assert(is_callable<F, A*>::value, "");
+
+  static_assert(is_callable<F, const A>::value, "");
+  static_assert(is_callable<F, const A&>::value, "");
+  static_assert(is_callable<F, const A*>::value, "");
+
+  static_assert(!is_callable<F, volatile A>::value, "");
+  static_assert(!is_callable<F, volatile A&>::value, "");
+  static_assert(!is_callable<F, volatile A*>::value, "");
+
+  static_assert(!is_callable<F, const volatile A>::value, "");
+  static_assert(!is_callable<F, const volatile A&>::value, "");
+  static_assert(!is_callable<F, const volatile A*>::value, "");
+}
+
+template <class F>
+void test_sfinae_volatile_fun0(F f) {
+  static_assert(is_callable<F, A>::value, "");
+  static_assert(is_callable<F, A&>::value, "");
+  static_assert(is_callable<F, A*>::value, "");
+
+  static_assert(!is_callable<F, const A>::value, "");
+  static_assert(!is_callable<F, const A&>::value, "");
+  static_assert(!is_callable<F, const A*>::value, "");
+
+  static_assert(is_callable<F, volatile A>::value, "");
+  static_assert(is_callable<F, volatile A&>::value, "");
+  static_assert(is_callable<F, volatile A*>::value, "");
+
+  static_assert(!is_callable<F, const volatile A>::value, "");
+  static_assert(!is_callable<F, const volatile A&>::value, "");
+  static_assert(!is_callable<F, const volatile A*>::value, "");
+}
+
+template <class F>
+void test_sfinae_const_volatile_fun0(F f) {
+  static_assert(is_callable<F, A>::value, "");
+  static_assert(is_callable<F, A&>::value, "");
+  static_assert(is_callable<F, A*>::value, "");
+
+  static_assert(is_callable<F, const A>::value, "");
+  static_assert(is_callable<F, const A&>::value, "");
+  static_assert(is_callable<F, const A*>::value, "");
+
+  static_assert(is_callable<F, volatile A>::value, "");
+  static_assert(is_callable<F, volatile A&>::value, "");
+  static_assert(is_callable<F, volatile A*>::value, "");
+
+  static_assert(is_callable<F, const volatile A>::value, "");
+  static_assert(is...
[truncated]

Previously, SFINAE constraints and exception specification propagation
were missing in the return type of libc++'s `std::mem_fn`. The
requirements on expression-equivalence (or even plain "equivalent" in
pre-C++20 specification) in [func.memfn] are actually requiring them.

This PR adds the missed stuffs.

Drive-by changes:
- removing no longer used `__invoke_return`,
- updating synopsis comments in several files, and
- merging several test files for `mem_fn` into one.
Copy link
Member

@ldionne ldionne left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! A few questions but this LGTM.

const F& cf = f;
assert(cf(ap) == 'a');

#if TEST_STD_VER >= 17
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you only checking for noexcept-ness in >= C++17?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In earlier modes, the stored member function pointer couldn't propagate noexcept-ness, because noexcept-specifier is made a part of type only since C++17 via P0012R1.

I'm not sure whether the behavioral change of the C++ core language should be reflected here.

struct is_callable : is_callable_impl<void, Func, Args...>::type {};

template <class F>
void test_sfinae_data(F) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you confirm that those are the tests you added for the issue you fixed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think SFINAE on pointer to data members is a bit beyond, but still related to the the original examples. When pm is a pointer to data member, std::invoke(pm, x, y) (or the INVOKE operation in the [func.require]/1) causes substitutioin failure, and std::mem_fn(pm)(x, y) should behave the same.

@frederick-vs-ja frederick-vs-ja merged commit a061d4d into llvm:main Oct 15, 2024
68 checks passed
@frederick-vs-ja frederick-vs-ja deleted the mem_fn-equivalence branch October 15, 2024 14:33
DanielCChen pushed a commit to DanielCChen/llvm-project that referenced this pull request Oct 16, 2024
Previously, SFINAE constraints and exception specification propagation
were missing in the return type of libc++'s `std::mem_fn`. The
requirements on expression-equivalence (or even plain "equivalent" in
pre-C++20 specification) in [func.memfn] are actually requiring them.

This PR adds the missed stuffs. Fixes llvm#86043.

Drive-by changes:
- removing no longer used `__invoke_return`,
- updating synopsis comments in several files, and
- merging several test files for `mem_fn` into one.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[libc++] The call operator of std::mem_fn(pm) isn't SFINAE-friendly and doesn't propagate noexcept
3 participants