diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -108,10 +108,12 @@ __functional_base __functional/binary_function.h __functional/binary_negate.h + __functional/bind_back.h __functional/bind_front.h __functional/bind.h __functional/binder1st.h __functional/binder2nd.h + __functional/compose.h __functional/default_searcher.h __functional/function.h __functional/hash.h diff --git a/libcxx/include/__functional/bind_back.h b/libcxx/include/__functional/bind_back.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__functional/bind_back.h @@ -0,0 +1,65 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP___FUNCTIONAL_BIND_BACK_H +#define _LIBCPP___FUNCTIONAL_BIND_BACK_H + +#include <__config> +#include <__functional/invoke.h> +#include <__functional/perfect_forward.h> +#include <__utility/forward.h> +#include <__utility/integer_sequence.h> +#include +#include + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +#pragma GCC system_header +#endif + +_LIBCPP_BEGIN_NAMESPACE_STD + +#if _LIBCPP_STD_VER > 17 + +template > +struct __bind_back_op; + +template +struct __bind_back_op<_NBound, index_sequence<_Ip...>> { + template + _LIBCPP_HIDE_FROM_ABI + constexpr auto operator()(_Fn&& __f, _Bound&& __bound, _Args&& ...__args) const + noexcept(noexcept(_VSTD::invoke(_VSTD::forward<_Fn>(__f), _VSTD::forward<_Args>(__args)..., _VSTD::get<_Ip>(_VSTD::forward<_Bound>(__bound))...))) + -> decltype( _VSTD::invoke(_VSTD::forward<_Fn>(__f), _VSTD::forward<_Args>(__args)..., _VSTD::get<_Ip>(_VSTD::forward<_Bound>(__bound))...)) + { return _VSTD::invoke(_VSTD::forward<_Fn>(__f), _VSTD::forward<_Args>(__args)..., _VSTD::get<_Ip>(_VSTD::forward<_Bound>(__bound))...); } +}; + +template +struct __bind_back_t : __perfect_forward<__bind_back_op>, _Fn, _BoundArgs> { + using __perfect_forward<__bind_back_op>, _Fn, _BoundArgs>::__perfect_forward; +}; + +template , _Fn>, + is_move_constructible>, + is_constructible, _Args>..., + is_move_constructible>... + >::value +>> +_LIBCPP_HIDE_FROM_ABI +constexpr auto __bind_back(_Fn&& __f, _Args&&... __args) + noexcept(noexcept(__bind_back_t, tuple...>>(_VSTD::forward<_Fn>(__f), _VSTD::forward_as_tuple(_VSTD::forward<_Args>(__args)...)))) + -> decltype( __bind_back_t, tuple...>>(_VSTD::forward<_Fn>(__f), _VSTD::forward_as_tuple(_VSTD::forward<_Args>(__args)...))) + { return __bind_back_t, tuple...>>(_VSTD::forward<_Fn>(__f), _VSTD::forward_as_tuple(_VSTD::forward<_Args>(__args)...)); } + +#endif // _LIBCPP_STD_VER > 17 + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP___FUNCTIONAL_BIND_BACK_H diff --git a/libcxx/include/__functional/compose.h b/libcxx/include/__functional/compose.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__functional/compose.h @@ -0,0 +1,52 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP___FUNCTIONAL_COMPOSE_H +#define _LIBCPP___FUNCTIONAL_COMPOSE_H + +#include <__config> +#include <__functional/invoke.h> +#include <__functional/perfect_forward.h> +#include <__utility/forward.h> +#include + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +#pragma GCC system_header +#endif + +_LIBCPP_BEGIN_NAMESPACE_STD + +#if _LIBCPP_STD_VER > 17 + +struct __compose_op { + template + _LIBCPP_HIDE_FROM_ABI + constexpr auto operator()(_Fn1&& __f1, _Fn2&& __f2, _Args&&... __args) const + noexcept(noexcept(_VSTD::invoke(_VSTD::forward<_Fn1>(__f1), _VSTD::invoke(_VSTD::forward<_Fn2>(__f2), _VSTD::forward<_Args>(__args)...)))) + -> decltype( _VSTD::invoke(_VSTD::forward<_Fn1>(__f1), _VSTD::invoke(_VSTD::forward<_Fn2>(__f2), _VSTD::forward<_Args>(__args)...))) + { return _VSTD::invoke(_VSTD::forward<_Fn1>(__f1), _VSTD::invoke(_VSTD::forward<_Fn2>(__f2), _VSTD::forward<_Args>(__args)...)); } +}; + +template +struct __compose_t : __perfect_forward<__compose_op, _Fn1, _Fn2> { + using __perfect_forward<__compose_op, _Fn1, _Fn2>::__perfect_forward; +}; + +template +_LIBCPP_HIDE_FROM_ABI +constexpr auto __compose(_Fn1&& __f1, _Fn2&& __f2) + noexcept(noexcept(__compose_t, decay_t<_Fn2>>(_VSTD::forward<_Fn1>(__f1), _VSTD::forward<_Fn2>(__f2)))) + -> decltype( __compose_t, decay_t<_Fn2>>(_VSTD::forward<_Fn1>(__f1), _VSTD::forward<_Fn2>(__f2))) + { return __compose_t, decay_t<_Fn2>>(_VSTD::forward<_Fn1>(__f1), _VSTD::forward<_Fn2>(__f2)); } + +#endif // _LIBCPP_STD_VER > 17 + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP___FUNCTIONAL_COMPOSE_H diff --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap --- a/libcxx/include/module.modulemap +++ b/libcxx/include/module.modulemap @@ -419,15 +419,17 @@ module binary_function { private header "__functional/binary_function.h" } module binary_negate { private header "__functional/binary_negate.h" } module bind { private header "__functional/bind.h" } + module bind_back { private header "__functional/bind_back.h" } module bind_front { private header "__functional/bind_front.h" } module binder1st { private header "__functional/binder1st.h" } module binder2nd { private header "__functional/binder2nd.h" } + module compose { private header "__functional/compose.h" } module default_searcher { private header "__functional/default_searcher.h" } module function { private header "__functional/function.h" } module hash { private header "__functional/hash.h" } module identity { private header "__functional/identity.h" } - module is_transparent { private header "__functional/is_transparent.h" } module invoke { private header "__functional/invoke.h" } + module is_transparent { private header "__functional/is_transparent.h" } module mem_fn { private header "__functional/mem_fn.h" } module mem_fun_ref { private header "__functional/mem_fun_ref.h" } module not_fn { private header "__functional/not_fn.h" } diff --git a/libcxx/test/libcxx/diagnostics/detail.headers/functional/bind_back.module.verify.cpp b/libcxx/test/libcxx/diagnostics/detail.headers/functional/bind_back.module.verify.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/diagnostics/detail.headers/functional/bind_back.module.verify.cpp @@ -0,0 +1,16 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// REQUIRES: modules-build + +// WARNING: This test was generated by 'generate_private_header_tests.py' +// and should not be edited manually. + +// expected-error@*:* {{use of private header from outside its module: '__functional/bind_back.h'}} +#include <__functional/bind_back.h> diff --git a/libcxx/test/libcxx/diagnostics/detail.headers/functional/compose.module.verify.cpp b/libcxx/test/libcxx/diagnostics/detail.headers/functional/compose.module.verify.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/diagnostics/detail.headers/functional/compose.module.verify.cpp @@ -0,0 +1,16 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// REQUIRES: modules-build + +// WARNING: This test was generated by 'generate_private_header_tests.py' +// and should not be edited manually. + +// expected-error@*:* {{use of private header from outside its module: '__functional/compose.h'}} +#include <__functional/compose.h> diff --git a/libcxx/test/libcxx/utilities/function.objects/func.bind.partial/bind_back.pass.cpp b/libcxx/test/libcxx/utilities/function.objects/func.bind.partial/bind_back.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/utilities/function.objects/func.bind.partial/bind_back.pass.cpp @@ -0,0 +1,417 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 + +// functional + +// template +// constexpr unspecified __bind_back(F&&, Args&&...); + +// This isn't part of the standard, however we use it internally and there is a +// chance that it will be added to the standard, so we implement those tests +// as-if it were part of the spec. + +#include <__functional/bind_back.h> // TODO: Remove this if it gets added to + +#include +#include +#include +#include + +#include "callable_types.h" +#include "test_macros.h" + +struct CopyMoveInfo { + enum { none, copy, move } copy_kind; + + constexpr CopyMoveInfo() : copy_kind(none) {} + constexpr CopyMoveInfo(CopyMoveInfo const&) : copy_kind(copy) {} + constexpr CopyMoveInfo(CopyMoveInfo&&) : copy_kind(move) {} +}; + +template +struct is_bind_backable { + template + static auto test(int) + -> decltype((void)std::__bind_back(std::declval()...), std::true_type()); + + template + static std::false_type test(...); + + static constexpr bool value = decltype(test(0))::value; +}; + +struct NotCopyMove { + NotCopyMove() = delete; + NotCopyMove(const NotCopyMove&) = delete; + NotCopyMove(NotCopyMove&&) = delete; + template + void operator()(Args&& ...) const { } +}; + +struct NonConstCopyConstructible { + explicit NonConstCopyConstructible() {} + NonConstCopyConstructible(NonConstCopyConstructible&) {} +}; + +struct MoveConstructible { + explicit MoveConstructible() {} + MoveConstructible(MoveConstructible&&) {} +}; + +struct MakeTuple { + template + constexpr auto operator()(Args&& ...args) const { + return std::make_tuple(std::forward(args)...); + } +}; + +template +struct Elem { + template + constexpr bool operator==(Elem const&) const + { return X == Y; } +}; + +constexpr bool test() { + // Bind arguments, call without arguments + { + { + auto f = std::__bind_back(MakeTuple{}); + assert(f() == std::make_tuple()); + } + { + auto f = std::__bind_back(MakeTuple{}, Elem<1>{}); + assert(f() == std::make_tuple(Elem<1>{})); + } + { + auto f = std::__bind_back(MakeTuple{}, Elem<1>{}, Elem<2>{}); + assert(f() == std::make_tuple(Elem<1>{}, Elem<2>{})); + } + { + auto f = std::__bind_back(MakeTuple{}, Elem<1>{}, Elem<2>{}, Elem<3>{}); + assert(f() == std::make_tuple(Elem<1>{}, Elem<2>{}, Elem<3>{})); + } + } + + // Bind no arguments, call with arguments + { + { + auto f = std::__bind_back(MakeTuple{}); + assert(f(Elem<1>{}) == std::make_tuple(Elem<1>{})); + } + { + auto f = std::__bind_back(MakeTuple{}); + assert(f(Elem<1>{}, Elem<2>{}) == std::make_tuple(Elem<1>{}, Elem<2>{})); + } + { + auto f = std::__bind_back(MakeTuple{}); + assert(f(Elem<1>{}, Elem<2>{}, Elem<3>{}) == std::make_tuple(Elem<1>{}, Elem<2>{}, Elem<3>{})); + } + } + + // Bind arguments, call with arguments + { + { + auto f = std::__bind_back(MakeTuple{}, Elem<1>{}); + assert(f(Elem<10>{}) == std::make_tuple(Elem<10>{}, Elem<1>{})); + } + { + auto f = std::__bind_back(MakeTuple{}, Elem<1>{}, Elem<2>{}); + assert(f(Elem<10>{}) == std::make_tuple(Elem<10>{}, Elem<1>{}, Elem<2>{})); + } + { + auto f = std::__bind_back(MakeTuple{}, Elem<1>{}, Elem<2>{}, Elem<3>{}); + assert(f(Elem<10>{}) == std::make_tuple(Elem<10>{}, Elem<1>{}, Elem<2>{}, Elem<3>{})); + } + + { + auto f = std::__bind_back(MakeTuple{}, Elem<1>{}); + assert(f(Elem<10>{}, Elem<11>{}) == std::make_tuple(Elem<10>{}, Elem<11>{}, Elem<1>{})); + } + { + auto f = std::__bind_back(MakeTuple{}, Elem<1>{}, Elem<2>{}); + assert(f(Elem<10>{}, Elem<11>{}) == std::make_tuple(Elem<10>{}, Elem<11>{}, Elem<1>{}, Elem<2>{})); + } + { + auto f = std::__bind_back(MakeTuple{}, Elem<1>{}, Elem<2>{}, Elem<3>{}); + assert(f(Elem<10>{}, Elem<11>{}) == std::make_tuple(Elem<10>{}, Elem<11>{}, Elem<1>{}, Elem<2>{}, Elem<3>{})); + } + } + + // Basic tests with fundamental types + { + int n = 2; + int m = 1; + auto add = [](int x, int y) { return x + y; }; + auto addN = [](int a, int b, int c, int d, int e, int f) { + return a + b + c + d + e + f; + }; + + auto a = std::__bind_back(add, m, n); + assert(a() == 3); + + auto b = std::__bind_back(addN, m, n, m, m, m, m); + assert(b() == 7); + + auto c = std::__bind_back(addN, n, m); + assert(c(1, 1, 1, 1) == 7); + + auto f = std::__bind_back(add, n); + assert(f(3) == 5); + + auto g = std::__bind_back(add, n, 1); + assert(g() == 3); + + auto h = std::__bind_back(addN, 1, 1, 1); + assert(h(2, 2, 2) == 9); + } + + // Make sure we don't treat std::reference_wrapper specially. + { + auto sub = [](std::reference_wrapper a, std::reference_wrapper b) { + return a.get() - b.get(); + }; + int i = 1, j = 2; + auto f = std::__bind_back(sub, std::ref(i)); + assert(f(std::ref(j)) == 2 - 1); + } + + // Make sure we can call a function that's a pointer to a member function. + { + struct MemberFunction { + constexpr bool foo(int, int) { return true; } + }; + MemberFunction value; + auto fn = std::__bind_back(&MemberFunction::foo, 0, 0); + assert(fn(value)); + } + + // Make sure that we copy the bound arguments into the unspecified-type. + { + auto add = [](int x, int y) { return x + y; }; + int n = 2; + auto i = std::__bind_back(add, n, 1); + n = 100; + assert(i() == 3); + } + + // Make sure we pass the bound arguments to the function object + // with the right value category. + { + { + auto wasCopied = [](CopyMoveInfo info) { + return info.copy_kind == CopyMoveInfo::copy; + }; + CopyMoveInfo info; + auto copied = std::__bind_back(wasCopied, info); + assert(copied()); + } + + { + auto wasMoved = [](CopyMoveInfo info) { + return info.copy_kind == CopyMoveInfo::move; + }; + CopyMoveInfo info; + auto moved = std::__bind_back(wasMoved, info); + assert(std::move(moved)()); + } + } + + // Make sure we call the correctly cv-ref qualified operator() based on the + // value category of the __bind_back unspecified-type. + { + struct F { + constexpr int operator()() & { return 1; } + constexpr int operator()() const& { return 2; } + constexpr int operator()() && { return 3; } + constexpr int operator()() const&& { return 4; } + }; + auto x = std::__bind_back(F{}); + using X = decltype(x); + assert(static_cast(x)() == 1); + assert(static_cast(x)() == 2); + assert(static_cast(x)() == 3); + assert(static_cast(x)() == 4); + } + + // Make sure the __bind_back unspecified-type is NOT invocable when the call would select a + // differently-qualified operator(). + // + // For example, if the call to `operator()() &` is ill-formed, the call to the unspecified-type + // should be ill-formed and not fall back to the `operator()() const&` overload. + { + // Make sure we delete the & overload when the underlying call isn't valid + { + struct F { + void operator()() & = delete; + void operator()() const&; + void operator()() &&; + void operator()() const&&; + }; + using X = decltype(std::__bind_back(F{})); + static_assert(!std::is_invocable_v); + static_assert( std::is_invocable_v); + static_assert( std::is_invocable_v); + static_assert( std::is_invocable_v); + } + + // There's no way to make sure we delete the const& overload when the underlying call isn't valid, + // so we can't check this one. + + // Make sure we delete the && overload when the underlying call isn't valid + { + struct F { + void operator()() &; + void operator()() const&; + void operator()() && = delete; + void operator()() const&&; + }; + using X = decltype(std::__bind_back(F{})); + static_assert( std::is_invocable_v); + static_assert( std::is_invocable_v); + static_assert(!std::is_invocable_v); + static_assert( std::is_invocable_v); + } + + // Make sure we delete the const&& overload when the underlying call isn't valid + { + struct F { + void operator()() &; + void operator()() const&; + void operator()() &&; + void operator()() const&& = delete; + }; + using X = decltype(std::__bind_back(F{})); + static_assert( std::is_invocable_v); + static_assert( std::is_invocable_v); + static_assert( std::is_invocable_v); + static_assert(!std::is_invocable_v); + } + } + + // Some examples by Tim Song + { + { + struct T { }; + struct F { + void operator()(T&&) const &; + void operator()(T&&) && = delete; + }; + using X = decltype(std::__bind_back(F{})); + static_assert(!std::is_invocable_v); + } + + { + struct T { }; + struct F { + void operator()(T const&) const; + void operator()(T&&) const = delete; + }; + using X = decltype(std::__bind_back(F{}, T{})); + static_assert(!std::is_invocable_v); + } + } + + // Test properties of the constructor of the unspecified-type returned by __bind_back. + { + { + MoveOnlyCallable value(true); + using RetT = decltype(std::__bind_back(std::move(value), 1)); + + static_assert( std::is_move_constructible::value); + static_assert(!std::is_copy_constructible::value); + static_assert(!std::is_move_assignable::value); + static_assert(!std::is_copy_assignable::value); + + auto ret = std::__bind_back(std::move(value), 1); + assert(ret()); + assert(ret(1, 2, 3)); + + auto ret1 = std::move(ret); + assert(!ret()); + assert(ret1()); + assert(ret1(1, 2, 3)); + } + { + CopyCallable value(true); + using RetT = decltype(std::__bind_back(value, 1)); + + static_assert( std::is_move_constructible::value); + static_assert( std::is_copy_constructible::value); + static_assert(!std::is_move_assignable::value); + static_assert(!std::is_copy_assignable::value); + + auto ret = std::__bind_back(value, 1); + assert(ret()); + assert(ret(1, 2, 3)); + + auto ret1 = std::move(ret); + assert(ret1()); + assert(ret1(1, 2, 3)); + + auto ret2 = std::__bind_back(std::move(value), 1); + assert(!ret()); + assert(ret2()); + assert(ret2(1, 2, 3)); + } + { + CopyAssignableWrapper value(true); + using RetT = decltype(std::__bind_back(value, 1)); + + static_assert(std::is_move_constructible::value); + static_assert(std::is_copy_constructible::value); + static_assert(std::is_move_assignable::value); + static_assert(std::is_copy_assignable::value); + } + { + MoveAssignableWrapper value(true); + using RetT = decltype(std::__bind_back(std::move(value), 1)); + + static_assert( std::is_move_constructible::value); + static_assert(!std::is_copy_constructible::value); + static_assert( std::is_move_assignable::value); + static_assert(!std::is_copy_assignable::value); + } + } + + // Make sure __bind_back is SFINAE friendly + { + using T = decltype(std::__bind_back(std::declval(), 1)); + static_assert(!std::is_invocable::value); + static_assert( std::is_invocable::value); + static_assert(!std::is_invocable::value); + static_assert(!std::is_invocable::value); + + static_assert(!std::is_constructible_v); + static_assert(!std::is_move_constructible_v); + static_assert(!is_bind_backable::value); + static_assert(!is_bind_backable::value); + + auto takeAnything = [](auto&& ...) { }; + static_assert(!std::is_constructible_v); + static_assert( std::is_move_constructible_v); + static_assert( is_bind_backable::value); + static_assert(!is_bind_backable::value); + + static_assert( std::is_constructible_v); + static_assert(!std::is_move_constructible_v); + static_assert(!is_bind_backable::value); + static_assert(!is_bind_backable::value); + } + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/libcxx/utilities/function.objects/func.bind.partial/compose.pass.cpp b/libcxx/test/libcxx/utilities/function.objects/func.bind.partial/compose.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/utilities/function.objects/func.bind.partial/compose.pass.cpp @@ -0,0 +1,81 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 + +// template +// constexpr unspecified __compose(F1&&, F2&&); + +#include <__functional/compose.h> +#include +#include +#include + +template +struct Elem { + template + constexpr bool operator==(Elem const&) const + { return X == Y; } +}; + +struct F { + template + constexpr auto operator()(Args&& ...args) const { + return std::make_tuple(Elem<888>{}, std::forward(args)...); + } +}; + +struct G { + template + constexpr auto operator()(Args&& ...args) const { + return std::make_tuple(Elem<999>{}, std::forward(args)...); + } +}; + +constexpr bool test() { + F const f; + G const g; + + { + auto c = std::__compose(f, g); + assert(c() == f(g())); + } + { + auto c = std::__compose(f, g); + assert(c(Elem<0>{}) == f(g(Elem<0>{}))); + } + { + auto c = std::__compose(f, g); + assert(c(Elem<0>{}, Elem<1>{}) == f(g(Elem<0>{}, Elem<1>{}))); + } + { + auto c = std::__compose(f, g); + assert(c(Elem<0>{}, Elem<1>{}, Elem<2>{}) == f(g(Elem<0>{}, Elem<1>{}, Elem<2>{}))); + } + + // Make sure we can call a function that's a pointer to a member function. + { + struct MemberFunction1 { + constexpr Elem<0> foo() { return {}; } + }; + struct MemberFunction2 { + constexpr MemberFunction1 bar() { return {}; } + }; + auto c = std::__compose(&MemberFunction1::foo, &MemberFunction2::bar); + assert(c(MemberFunction2{}) == Elem<0>{}); + } + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +}