forked from mirror/libcxx
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
617 lines
19 KiB
C++
617 lines
19 KiB
C++
//===----------------------------------------------------------------------===//
|
|
//
|
|
// The LLVM Compiler Infrastructure
|
|
//
|
|
// This file is dual licensed under the MIT and the University of Illinois Open
|
|
// Source Licenses. See LICENSE.TXT for details.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// UNSUPPORTED: c++98, c++03, c++11, c++14
|
|
|
|
// template <class F> unspecified not_fn(F&& f);
|
|
|
|
#include <functional>
|
|
#include <type_traits>
|
|
#include <string>
|
|
#include <cassert>
|
|
|
|
#include "test_macros.h"
|
|
#include "type_id.h"
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// CALLABLE TEST TYPES
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool returns_true() { return true; }
|
|
|
|
template <class Ret = bool>
|
|
struct MoveOnlyCallable {
|
|
MoveOnlyCallable(MoveOnlyCallable const&) = delete;
|
|
MoveOnlyCallable(MoveOnlyCallable&& other)
|
|
: value(other.value)
|
|
{ other.value = !other.value; }
|
|
|
|
template <class ...Args>
|
|
Ret operator()(Args&&...) { return Ret{value}; }
|
|
|
|
explicit MoveOnlyCallable(bool x) : value(x) {}
|
|
Ret value;
|
|
};
|
|
|
|
template <class Ret = bool>
|
|
struct CopyCallable {
|
|
CopyCallable(CopyCallable const& other)
|
|
: value(other.value) {}
|
|
|
|
CopyCallable(CopyCallable&& other)
|
|
: value(other.value) { other.value = !other.value; }
|
|
|
|
template <class ...Args>
|
|
Ret operator()(Args&&...) { return Ret{value}; }
|
|
|
|
explicit CopyCallable(bool x) : value(x) {}
|
|
Ret value;
|
|
};
|
|
|
|
|
|
template <class Ret = bool>
|
|
struct ConstCallable {
|
|
ConstCallable(ConstCallable const& other)
|
|
: value(other.value) {}
|
|
|
|
ConstCallable(ConstCallable&& other)
|
|
: value(other.value) { other.value = !other.value; }
|
|
|
|
template <class ...Args>
|
|
Ret operator()(Args&&...) const { return Ret{value}; }
|
|
|
|
explicit ConstCallable(bool x) : value(x) {}
|
|
Ret value;
|
|
};
|
|
|
|
|
|
|
|
template <class Ret = bool>
|
|
struct NoExceptCallable {
|
|
NoExceptCallable(NoExceptCallable const& other)
|
|
: value(other.value) {}
|
|
|
|
template <class ...Args>
|
|
Ret operator()(Args&&...) noexcept { return Ret{value}; }
|
|
|
|
template <class ...Args>
|
|
Ret operator()(Args&&...) const noexcept { return Ret{value}; }
|
|
|
|
explicit NoExceptCallable(bool x) : value(x) {}
|
|
Ret value;
|
|
};
|
|
|
|
struct CopyAssignableWrapper {
|
|
CopyAssignableWrapper(CopyAssignableWrapper const&) = default;
|
|
CopyAssignableWrapper(CopyAssignableWrapper&&) = default;
|
|
CopyAssignableWrapper& operator=(CopyAssignableWrapper const&) = default;
|
|
CopyAssignableWrapper& operator=(CopyAssignableWrapper &&) = default;
|
|
|
|
template <class ...Args>
|
|
bool operator()(Args&&...) { return value; }
|
|
|
|
explicit CopyAssignableWrapper(bool x) : value(x) {}
|
|
bool value;
|
|
};
|
|
|
|
|
|
struct MoveAssignableWrapper {
|
|
MoveAssignableWrapper(MoveAssignableWrapper const&) = delete;
|
|
MoveAssignableWrapper(MoveAssignableWrapper&&) = default;
|
|
MoveAssignableWrapper& operator=(MoveAssignableWrapper const&) = delete;
|
|
MoveAssignableWrapper& operator=(MoveAssignableWrapper &&) = default;
|
|
|
|
template <class ...Args>
|
|
bool operator()(Args&&...) { return value; }
|
|
|
|
explicit MoveAssignableWrapper(bool x) : value(x) {}
|
|
bool value;
|
|
};
|
|
|
|
struct MemFunCallable {
|
|
explicit MemFunCallable(bool x) : value(x) {}
|
|
|
|
bool return_value() const { return value; }
|
|
bool return_value_nc() { return value; }
|
|
bool value;
|
|
};
|
|
|
|
enum CallType : unsigned {
|
|
CT_None,
|
|
CT_NonConst = 1,
|
|
CT_Const = 2,
|
|
CT_LValue = 4,
|
|
CT_RValue = 8
|
|
};
|
|
|
|
inline constexpr CallType operator|(CallType LHS, CallType RHS) {
|
|
return static_cast<CallType>(static_cast<unsigned>(LHS) | static_cast<unsigned>(RHS));
|
|
}
|
|
|
|
struct ForwardingCallObject {
|
|
|
|
template <class ...Args>
|
|
bool operator()(Args&&...) & {
|
|
set_call<Args&&...>(CT_NonConst | CT_LValue);
|
|
return true;
|
|
}
|
|
|
|
template <class ...Args>
|
|
bool operator()(Args&&...) const & {
|
|
set_call<Args&&...>(CT_Const | CT_LValue);
|
|
return true;
|
|
}
|
|
|
|
// Don't allow the call operator to be invoked as an rvalue.
|
|
template <class ...Args>
|
|
bool operator()(Args&&...) && {
|
|
set_call<Args&&...>(CT_NonConst | CT_RValue);
|
|
return true;
|
|
}
|
|
|
|
template <class ...Args>
|
|
bool operator()(Args&&...) const && {
|
|
set_call<Args&&...>(CT_Const | CT_RValue);
|
|
return true;
|
|
}
|
|
|
|
template <class ...Args>
|
|
static void set_call(CallType type) {
|
|
assert(last_call_type == CT_None);
|
|
assert(last_call_args == nullptr);
|
|
last_call_type = type;
|
|
last_call_args = &makeArgumentID<Args...>();
|
|
}
|
|
|
|
template <class ...Args>
|
|
static bool check_call(CallType type) {
|
|
bool result =
|
|
last_call_type == type
|
|
&& last_call_args
|
|
&& *last_call_args == makeArgumentID<Args...>();
|
|
last_call_type = CT_None;
|
|
last_call_args = nullptr;
|
|
return result;
|
|
}
|
|
|
|
static CallType last_call_type;
|
|
static TypeID const* last_call_args;
|
|
};
|
|
|
|
CallType ForwardingCallObject::last_call_type = CT_None;
|
|
TypeID const* ForwardingCallObject::last_call_args = nullptr;
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// BOOL TEST TYPES
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
struct EvilBool {
|
|
static int bang_called;
|
|
|
|
EvilBool(EvilBool const&) = default;
|
|
EvilBool(EvilBool&&) = default;
|
|
|
|
friend EvilBool operator!(EvilBool const& other) {
|
|
++bang_called;
|
|
return EvilBool{!other.value};
|
|
}
|
|
|
|
private:
|
|
friend struct MoveOnlyCallable<EvilBool>;
|
|
friend struct CopyCallable<EvilBool>;
|
|
friend struct NoExceptCallable<EvilBool>;
|
|
|
|
explicit EvilBool(bool x) : value(x) {}
|
|
EvilBool& operator=(EvilBool const& other) = default;
|
|
|
|
public:
|
|
bool value;
|
|
};
|
|
|
|
int EvilBool::bang_called = 0;
|
|
|
|
struct ExplicitBool {
|
|
ExplicitBool(ExplicitBool const&) = default;
|
|
ExplicitBool(ExplicitBool&&) = default;
|
|
|
|
explicit operator bool() const { return value; }
|
|
|
|
private:
|
|
friend struct MoveOnlyCallable<ExplicitBool>;
|
|
friend struct CopyCallable<ExplicitBool>;
|
|
|
|
explicit ExplicitBool(bool x) : value(x) {}
|
|
ExplicitBool& operator=(bool x) {
|
|
value = x;
|
|
return *this;
|
|
}
|
|
|
|
bool value;
|
|
};
|
|
|
|
|
|
struct NoExceptEvilBool {
|
|
NoExceptEvilBool(NoExceptEvilBool const&) = default;
|
|
NoExceptEvilBool(NoExceptEvilBool&&) = default;
|
|
NoExceptEvilBool& operator=(NoExceptEvilBool const& other) = default;
|
|
|
|
explicit NoExceptEvilBool(bool x) : value(x) {}
|
|
|
|
friend NoExceptEvilBool operator!(NoExceptEvilBool const& other) noexcept {
|
|
return NoExceptEvilBool{!other.value};
|
|
}
|
|
|
|
bool value;
|
|
};
|
|
|
|
|
|
|
|
void constructor_tests()
|
|
{
|
|
{
|
|
using T = MoveOnlyCallable<bool>;
|
|
T value(true);
|
|
using RetT = decltype(std::not_fn(std::move(value)));
|
|
static_assert(std::is_move_constructible<RetT>::value, "");
|
|
static_assert(!std::is_copy_constructible<RetT>::value, "");
|
|
static_assert(!std::is_move_assignable<RetT>::value, "");
|
|
static_assert(!std::is_copy_assignable<RetT>::value, "");
|
|
auto ret = std::not_fn(std::move(value));
|
|
// test it was moved from
|
|
assert(value.value == false);
|
|
// test that ret() negates the original value 'true'
|
|
assert(ret() == false);
|
|
assert(ret(0, 0.0, "blah") == false);
|
|
// Move ret and test that it was moved from and that ret2 got the
|
|
// original value.
|
|
auto ret2 = std::move(ret);
|
|
assert(ret() == true);
|
|
assert(ret2() == false);
|
|
assert(ret2(42) == false);
|
|
}
|
|
{
|
|
using T = CopyCallable<bool>;
|
|
T value(false);
|
|
using RetT = decltype(std::not_fn(value));
|
|
static_assert(std::is_move_constructible<RetT>::value, "");
|
|
static_assert(std::is_copy_constructible<RetT>::value, "");
|
|
static_assert(!std::is_move_assignable<RetT>::value, "");
|
|
static_assert(!std::is_copy_assignable<RetT>::value, "");
|
|
auto ret = std::not_fn(value);
|
|
// test that value is unchanged (copied not moved)
|
|
assert(value.value == false);
|
|
// test 'ret' has the original value
|
|
assert(ret() == true);
|
|
assert(ret(42, 100) == true);
|
|
// move from 'ret' and check that 'ret2' has the original value.
|
|
auto ret2 = std::move(ret);
|
|
assert(ret() == false);
|
|
assert(ret2() == true);
|
|
assert(ret2("abc") == true);
|
|
}
|
|
{
|
|
using T = CopyAssignableWrapper;
|
|
T value(true);
|
|
T value2(false);
|
|
using RetT = decltype(std::not_fn(value));
|
|
static_assert(std::is_move_constructible<RetT>::value, "");
|
|
static_assert(std::is_copy_constructible<RetT>::value, "");
|
|
LIBCPP_STATIC_ASSERT(std::is_move_assignable<RetT>::value, "");
|
|
LIBCPP_STATIC_ASSERT(std::is_copy_assignable<RetT>::value, "");
|
|
auto ret = std::not_fn(value);
|
|
assert(ret() == false);
|
|
auto ret2 = std::not_fn(value2);
|
|
assert(ret2() == true);
|
|
#if defined(_LIBCPP_VERSION)
|
|
ret = ret2;
|
|
assert(ret() == true);
|
|
assert(ret2() == true);
|
|
#endif // _LIBCPP_VERSION
|
|
}
|
|
{
|
|
using T = MoveAssignableWrapper;
|
|
T value(true);
|
|
T value2(false);
|
|
using RetT = decltype(std::not_fn(std::move(value)));
|
|
static_assert(std::is_move_constructible<RetT>::value, "");
|
|
static_assert(!std::is_copy_constructible<RetT>::value, "");
|
|
LIBCPP_STATIC_ASSERT(std::is_move_assignable<RetT>::value, "");
|
|
static_assert(!std::is_copy_assignable<RetT>::value, "");
|
|
auto ret = std::not_fn(std::move(value));
|
|
assert(ret() == false);
|
|
auto ret2 = std::not_fn(std::move(value2));
|
|
assert(ret2() == true);
|
|
#if defined(_LIBCPP_VERSION)
|
|
ret = std::move(ret2);
|
|
assert(ret() == true);
|
|
#endif // _LIBCPP_VERSION
|
|
}
|
|
}
|
|
|
|
void return_type_tests()
|
|
{
|
|
using std::is_same;
|
|
{
|
|
using T = CopyCallable<bool>;
|
|
auto ret = std::not_fn(T{false});
|
|
static_assert(is_same<decltype(ret()), bool>::value, "");
|
|
static_assert(is_same<decltype(ret("abc")), bool>::value, "");
|
|
assert(ret() == true);
|
|
}
|
|
{
|
|
using T = CopyCallable<ExplicitBool>;
|
|
auto ret = std::not_fn(T{true});
|
|
static_assert(is_same<decltype(ret()), bool>::value, "");
|
|
static_assert(is_same<decltype(ret(std::string("abc"))), bool>::value, "");
|
|
assert(ret() == false);
|
|
}
|
|
{
|
|
using T = CopyCallable<EvilBool>;
|
|
auto ret = std::not_fn(T{false});
|
|
static_assert(is_same<decltype(ret()), EvilBool>::value, "");
|
|
EvilBool::bang_called = 0;
|
|
auto value_ret = ret();
|
|
assert(EvilBool::bang_called == 1);
|
|
assert(value_ret.value == true);
|
|
ret();
|
|
assert(EvilBool::bang_called == 2);
|
|
}
|
|
}
|
|
|
|
// Other tests only test using objects with call operators. Test various
|
|
// other callable types here.
|
|
void other_callable_types_test()
|
|
{
|
|
{ // test with function pointer
|
|
auto ret = std::not_fn(returns_true);
|
|
assert(ret() == false);
|
|
}
|
|
{ // test with lambda
|
|
auto returns_value = [](bool value) { return value; };
|
|
auto ret = std::not_fn(returns_value);
|
|
assert(ret(true) == false);
|
|
assert(ret(false) == true);
|
|
}
|
|
{ // test with pointer to member function
|
|
MemFunCallable mt(true);
|
|
const MemFunCallable mf(false);
|
|
auto ret = std::not_fn(&MemFunCallable::return_value);
|
|
assert(ret(mt) == false);
|
|
assert(ret(mf) == true);
|
|
assert(ret(&mt) == false);
|
|
assert(ret(&mf) == true);
|
|
}
|
|
{ // test with pointer to member function
|
|
MemFunCallable mt(true);
|
|
MemFunCallable mf(false);
|
|
auto ret = std::not_fn(&MemFunCallable::return_value_nc);
|
|
assert(ret(mt) == false);
|
|
assert(ret(mf) == true);
|
|
assert(ret(&mt) == false);
|
|
assert(ret(&mf) == true);
|
|
}
|
|
{ // test with pointer to member data
|
|
MemFunCallable mt(true);
|
|
const MemFunCallable mf(false);
|
|
auto ret = std::not_fn(&MemFunCallable::value);
|
|
assert(ret(mt) == false);
|
|
assert(ret(mf) == true);
|
|
assert(ret(&mt) == false);
|
|
assert(ret(&mf) == true);
|
|
}
|
|
}
|
|
|
|
void throws_in_constructor_test()
|
|
{
|
|
#ifndef TEST_HAS_NO_EXCEPTIONS
|
|
struct ThrowsOnCopy {
|
|
ThrowsOnCopy(ThrowsOnCopy const&) {
|
|
throw 42;
|
|
}
|
|
ThrowsOnCopy() = default;
|
|
bool operator()() const {
|
|
assert(false);
|
|
#if defined(TEST_COMPILER_C1XX)
|
|
__assume(0);
|
|
#else
|
|
__builtin_unreachable();
|
|
#endif
|
|
}
|
|
};
|
|
{
|
|
ThrowsOnCopy cp;
|
|
try {
|
|
(void)std::not_fn(cp);
|
|
assert(false);
|
|
} catch (int const& value) {
|
|
assert(value == 42);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void call_operator_sfinae_test() {
|
|
{ // wrong number of arguments
|
|
using T = decltype(std::not_fn(returns_true));
|
|
static_assert(std::is_invocable<T>::value, ""); // callable only with no args
|
|
static_assert(!std::is_invocable<T, bool>::value, "");
|
|
}
|
|
{ // violates const correctness (member function pointer)
|
|
using T = decltype(std::not_fn(&MemFunCallable::return_value_nc));
|
|
static_assert(std::is_invocable<T, MemFunCallable&>::value, "");
|
|
static_assert(!std::is_invocable<T, const MemFunCallable&>::value, "");
|
|
}
|
|
{ // violates const correctness (call object)
|
|
using Obj = CopyCallable<bool>;
|
|
using NCT = decltype(std::not_fn(Obj{true}));
|
|
using CT = const NCT;
|
|
static_assert(std::is_invocable<NCT>::value, "");
|
|
static_assert(!std::is_invocable<CT>::value, "");
|
|
}
|
|
{ // returns bad type with no operator!
|
|
auto fn = [](auto x) { return x; };
|
|
using T = decltype(std::not_fn(fn));
|
|
static_assert(std::is_invocable<T, bool>::value, "");
|
|
static_assert(!std::is_invocable<T, std::string>::value, "");
|
|
}
|
|
}
|
|
|
|
void call_operator_forwarding_test()
|
|
{
|
|
using Fn = ForwardingCallObject;
|
|
auto obj = std::not_fn(Fn{});
|
|
const auto& c_obj = obj;
|
|
{ // test zero args
|
|
obj();
|
|
assert(Fn::check_call<>(CT_NonConst | CT_LValue));
|
|
std::move(obj)();
|
|
assert(Fn::check_call<>(CT_NonConst | CT_RValue));
|
|
c_obj();
|
|
assert(Fn::check_call<>(CT_Const | CT_LValue));
|
|
std::move(c_obj)();
|
|
assert(Fn::check_call<>(CT_Const | CT_RValue));
|
|
}
|
|
{ // test value categories
|
|
int x = 42;
|
|
const int cx = 42;
|
|
obj(x);
|
|
assert(Fn::check_call<int&>(CT_NonConst | CT_LValue));
|
|
obj(cx);
|
|
assert(Fn::check_call<const int&>(CT_NonConst | CT_LValue));
|
|
obj(std::move(x));
|
|
assert(Fn::check_call<int&&>(CT_NonConst | CT_LValue));
|
|
obj(std::move(cx));
|
|
assert(Fn::check_call<const int&&>(CT_NonConst | CT_LValue));
|
|
obj(42);
|
|
assert(Fn::check_call<int&&>(CT_NonConst | CT_LValue));
|
|
}
|
|
{ // test value categories - rvalue
|
|
int x = 42;
|
|
const int cx = 42;
|
|
std::move(obj)(x);
|
|
assert(Fn::check_call<int&>(CT_NonConst | CT_RValue));
|
|
std::move(obj)(cx);
|
|
assert(Fn::check_call<const int&>(CT_NonConst | CT_RValue));
|
|
std::move(obj)(std::move(x));
|
|
assert(Fn::check_call<int&&>(CT_NonConst | CT_RValue));
|
|
std::move(obj)(std::move(cx));
|
|
assert(Fn::check_call<const int&&>(CT_NonConst | CT_RValue));
|
|
std::move(obj)(42);
|
|
assert(Fn::check_call<int&&>(CT_NonConst | CT_RValue));
|
|
}
|
|
{ // test value categories - const call
|
|
int x = 42;
|
|
const int cx = 42;
|
|
c_obj(x);
|
|
assert(Fn::check_call<int&>(CT_Const | CT_LValue));
|
|
c_obj(cx);
|
|
assert(Fn::check_call<const int&>(CT_Const | CT_LValue));
|
|
c_obj(std::move(x));
|
|
assert(Fn::check_call<int&&>(CT_Const | CT_LValue));
|
|
c_obj(std::move(cx));
|
|
assert(Fn::check_call<const int&&>(CT_Const | CT_LValue));
|
|
c_obj(42);
|
|
assert(Fn::check_call<int&&>(CT_Const | CT_LValue));
|
|
}
|
|
{ // test value categories - const call rvalue
|
|
int x = 42;
|
|
const int cx = 42;
|
|
std::move(c_obj)(x);
|
|
assert(Fn::check_call<int&>(CT_Const | CT_RValue));
|
|
std::move(c_obj)(cx);
|
|
assert(Fn::check_call<const int&>(CT_Const | CT_RValue));
|
|
std::move(c_obj)(std::move(x));
|
|
assert(Fn::check_call<int&&>(CT_Const | CT_RValue));
|
|
std::move(c_obj)(std::move(cx));
|
|
assert(Fn::check_call<const int&&>(CT_Const | CT_RValue));
|
|
std::move(c_obj)(42);
|
|
assert(Fn::check_call<int&&>(CT_Const | CT_RValue));
|
|
}
|
|
{ // test multi arg
|
|
const double y = 3.14;
|
|
std::string s = "abc";
|
|
obj(42, std::move(y), s, std::string{"foo"});
|
|
Fn::check_call<int&&, const double&&, std::string&, std::string&&>(CT_NonConst | CT_LValue);
|
|
std::move(obj)(42, std::move(y), s, std::string{"foo"});
|
|
Fn::check_call<int&&, const double&&, std::string&, std::string&&>(CT_NonConst | CT_RValue);
|
|
c_obj(42, std::move(y), s, std::string{"foo"});
|
|
Fn::check_call<int&&, const double&&, std::string&, std::string&&>(CT_Const | CT_LValue);
|
|
std::move(c_obj)(42, std::move(y), s, std::string{"foo"});
|
|
Fn::check_call<int&&, const double&&, std::string&, std::string&&>(CT_Const | CT_RValue);
|
|
}
|
|
}
|
|
|
|
void call_operator_noexcept_test()
|
|
{
|
|
{
|
|
using T = ConstCallable<bool>;
|
|
T value(true);
|
|
auto ret = std::not_fn(value);
|
|
static_assert(!noexcept(ret()), "call should not be noexcept");
|
|
auto const& cret = ret;
|
|
static_assert(!noexcept(cret()), "call should not be noexcept");
|
|
}
|
|
{
|
|
using T = NoExceptCallable<bool>;
|
|
T value(true);
|
|
auto ret = std::not_fn(value);
|
|
LIBCPP_STATIC_ASSERT(noexcept(!_VSTD::__invoke(value)), "");
|
|
#if TEST_STD_VER > 14
|
|
static_assert(noexcept(!std::invoke(value)), "");
|
|
#endif
|
|
static_assert(noexcept(ret()), "call should be noexcept");
|
|
auto const& cret = ret;
|
|
static_assert(noexcept(cret()), "call should be noexcept");
|
|
}
|
|
{
|
|
using T = NoExceptCallable<NoExceptEvilBool>;
|
|
T value(true);
|
|
auto ret = std::not_fn(value);
|
|
static_assert(noexcept(ret()), "call should not be noexcept");
|
|
auto const& cret = ret;
|
|
static_assert(noexcept(cret()), "call should not be noexcept");
|
|
}
|
|
{
|
|
using T = NoExceptCallable<EvilBool>;
|
|
T value(true);
|
|
auto ret = std::not_fn(value);
|
|
static_assert(!noexcept(ret()), "call should not be noexcept");
|
|
auto const& cret = ret;
|
|
static_assert(!noexcept(cret()), "call should not be noexcept");
|
|
}
|
|
}
|
|
|
|
void test_lwg2767() {
|
|
// See https://cplusplus.github.io/LWG/lwg-defects.html#2767
|
|
struct Abstract { virtual void f() const = 0; };
|
|
struct Derived : public Abstract { void f() const {} };
|
|
struct F { bool operator()(Abstract&&) { return false; } };
|
|
{
|
|
Derived d;
|
|
Abstract &a = d;
|
|
bool b = std::not_fn(F{})(std::move(a));
|
|
assert(b);
|
|
}
|
|
}
|
|
|
|
int main()
|
|
{
|
|
constructor_tests();
|
|
return_type_tests();
|
|
other_callable_types_test();
|
|
throws_in_constructor_test();
|
|
call_operator_sfinae_test(); // somewhat of an extension
|
|
call_operator_forwarding_test();
|
|
call_operator_noexcept_test();
|
|
test_lwg2767();
|
|
}
|