| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| // RUN: %clang_cc1 -emit-pch -std=c++23 -o %t %s | ||
| // RUN: %clang_cc1 -include-pch %t -verify -fsyntax-only -DTEST -std=c++23 %s | ||
|
|
||
| // Test that dependence of 'this' and DREs due to by-value capture by a | ||
| // lambda with an explicit object parameter is serialised/deserialised | ||
| // properly. | ||
|
|
||
| #ifndef HEADER | ||
| #define HEADER | ||
| struct S { | ||
| int x; | ||
| auto f() { | ||
| return [*this] (this auto&&) { | ||
| int y; | ||
| x = 42; | ||
|
|
||
| const auto l = [y] (this auto&&) { y = 42; }; | ||
| l(); | ||
| }; | ||
| } | ||
| }; | ||
| #endif | ||
|
|
||
| // expected-error@* {{read-only variable is not assignable}} | ||
| // expected-error@* {{cannot assign to a variable captured by copy in a non-mutable lambda}} | ||
| // expected-note@* 2 {{in instantiation of}} | ||
|
|
||
| #ifdef TEST | ||
| void f() { | ||
| const auto l = S{}.f(); | ||
| l(); // expected-note {{in instantiation of}} | ||
| } | ||
| #endif | ||
|
|
||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| // RUN: %clang_cc1 -verify %s | ||
| // RUN: %clang_cc1 -verify=good -Wno-tentative-definition-array %s | ||
| // good-no-diagnostics | ||
|
|
||
| int foo[]; // expected-warning {{tentative array definition assumed to have one element}} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| // RUN: %clang_cc1 -fsyntax-only -verify -Wundefined-func-template %s | ||
|
|
||
| namespace GH74016 { | ||
| template <typename T> class B { | ||
| public: | ||
| constexpr void foo(const T &) { bar(1); } | ||
| virtual constexpr void bar(unsigned int) = 0; | ||
| }; | ||
|
|
||
| template <typename T> class D : public B<T> { | ||
| public: | ||
| constexpr void bar(unsigned int) override {} | ||
| }; | ||
|
|
||
| void test() { | ||
| auto t = D<int>(); | ||
| t.foo(0); | ||
| } | ||
| }; | ||
|
|
||
| namespace call_pure_virtual_function_from_virtual { | ||
| template <typename T> class B { | ||
| public: | ||
| const void foo(const T &) { B::bar(1); } // expected-warning {{instantiation of function 'call_pure_virtual_function_from_virtual::B<int>::bar' required here, but no definition is available}} | ||
| // expected-note@-1 {{add an explicit instantiation declaration to suppress this warning if 'call_pure_virtual_function_from_virtual::B<int>::bar' is explicitly instantiated in another translation unit}} | ||
| virtual const void bar(unsigned int) = 0; // expected-note {{forward declaration of template entity is here}} | ||
| }; | ||
|
|
||
| template <typename T> class D : public B<T> { | ||
| public: | ||
| const void bar(unsigned int) override {} | ||
| }; | ||
|
|
||
| void test() { | ||
| auto t = D<int>(); | ||
| t.foo(0); // expected-note {{in instantiation of member function 'call_pure_virtual_function_from_virtual::B<int>::foo' requested here}} | ||
| } | ||
| }; | ||
|
|
||
| namespace non_pure_virtual_function { | ||
| template <typename T> class B { | ||
| public: | ||
| constexpr void foo(const T &) { bar(1); } | ||
|
|
||
| virtual constexpr void bar(unsigned int); // expected-warning {{inline function 'non_pure_virtual_function::B<int>::bar' is not defined}} | ||
| // expected-note@-1 {{forward declaration of template entity is here}} | ||
| // expected-note@-2 {{forward declaration of template entity is here}} | ||
| // expected-note@-3 {{forward declaration of template entity is here}} | ||
| }; | ||
|
|
||
| template <typename T> class D : public B<T> { // expected-warning {{instantiation of function 'non_pure_virtual_function::B<int>::bar' required here, but no definition is available}} | ||
| // expected-warning@-1 {{instantiation of function 'non_pure_virtual_function::B<int>::bar' required here, but no definition is available}} | ||
| // expected-warning@-2 {{instantiation of function 'non_pure_virtual_function::B<int>::bar' required here, but no definition is available}} | ||
| // expected-note@-3 {{add an explicit instantiation declaration to suppress this warning if 'non_pure_virtual_function::B<int>::bar' is explicitly instantiated in another translation unit}} | ||
| // expected-note@-4 {{add an explicit instantiation declaration to suppress this warning if 'non_pure_virtual_function::B<int>::bar' is explicitly instantiated in another translation unit}} | ||
| // expected-note@-5 {{add an explicit instantiation declaration to suppress this warning if 'non_pure_virtual_function::B<int>::bar' is explicitly instantiated in another translation unit}} | ||
| // expected-note@-6 {{used here}} | ||
|
|
||
| public: | ||
| constexpr void bar(unsigned int) override { } | ||
| }; | ||
|
|
||
| void test() { | ||
| auto t = D<int>(); | ||
| t.foo(0); | ||
| } | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| add_clang_unittest(InterpTests | ||
| Descriptor.cpp | ||
| toAPValue.cpp | ||
| ) | ||
|
|
||
| clang_target_link_libraries(InterpTests | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| #include "../../../lib/AST/Interp/Context.h" | ||
| #include "../../../lib/AST/Interp/Descriptor.h" | ||
| #include "../../../lib/AST/Interp/Program.h" | ||
| #include "clang/AST/ASTContext.h" | ||
| #include "clang/AST/Decl.h" | ||
| #include "clang/ASTMatchers/ASTMatchFinder.h" | ||
| #include "clang/ASTMatchers/ASTMatchers.h" | ||
| #include "clang/Tooling/Tooling.h" | ||
| #include "gtest/gtest.h" | ||
|
|
||
| using namespace clang; | ||
| using namespace clang::interp; | ||
| using namespace clang::ast_matchers; | ||
|
|
||
| /// Test the various toAPValue implementations. | ||
| TEST(ToAPValue, Pointers) { | ||
| constexpr char Code[] = | ||
| "struct A { bool a; bool z; };\n" | ||
| "struct S {\n" | ||
| " A a[3];\n" | ||
| "};\n" | ||
| "constexpr S d = {{{true, false}, {false, true}, {false, false}}};\n" | ||
| "constexpr const bool *b = &d.a[1].z;\n"; | ||
|
|
||
| auto AST = tooling::buildASTFromCodeWithArgs( | ||
| Code, {"-fexperimental-new-constant-interpreter"}); | ||
|
|
||
| auto &Ctx = AST->getASTContext().getInterpContext(); | ||
| Program &Prog = Ctx.getProgram(); | ||
|
|
||
| auto getDecl = [&](const char *Name) -> const ValueDecl * { | ||
| auto Nodes = | ||
| match(valueDecl(hasName(Name)).bind("var"), AST->getASTContext()); | ||
| assert(Nodes.size() == 1); | ||
| const auto *D = Nodes[0].getNodeAs<ValueDecl>("var"); | ||
| assert(D); | ||
| return D; | ||
| }; | ||
| auto getGlobalPtr = [&](const char *Name) -> Pointer { | ||
| const VarDecl *D = cast<VarDecl>(getDecl(Name)); | ||
| return Prog.getPtrGlobal(*Prog.getGlobal(D)); | ||
| }; | ||
|
|
||
| const Pointer &GP = getGlobalPtr("b"); | ||
| const Pointer &P = GP.deref<Pointer>(); | ||
| ASSERT_TRUE(P.isLive()); | ||
| APValue A = P.toAPValue(); | ||
| ASSERT_TRUE(A.isLValue()); | ||
| ASSERT_TRUE(A.hasLValuePath()); | ||
| const auto &Path = A.getLValuePath(); | ||
| ASSERT_EQ(Path.size(), 3u); | ||
| ASSERT_EQ(A.getLValueBase(), getDecl("d")); | ||
| } | ||
|
|
||
| TEST(ToAPValue, FunctionPointers) { | ||
| constexpr char Code[] = " constexpr bool foo() { return true; }\n" | ||
| " constexpr bool (*func)() = foo;\n"; | ||
|
|
||
| auto AST = tooling::buildASTFromCodeWithArgs( | ||
| Code, {"-fexperimental-new-constant-interpreter"}); | ||
|
|
||
| auto &Ctx = AST->getASTContext().getInterpContext(); | ||
| Program &Prog = Ctx.getProgram(); | ||
|
|
||
| auto getDecl = [&](const char *Name) -> const ValueDecl * { | ||
| auto Nodes = | ||
| match(valueDecl(hasName(Name)).bind("var"), AST->getASTContext()); | ||
| assert(Nodes.size() == 1); | ||
| const auto *D = Nodes[0].getNodeAs<ValueDecl>("var"); | ||
| assert(D); | ||
| return D; | ||
| }; | ||
|
|
||
| auto getGlobalPtr = [&](const char *Name) -> Pointer { | ||
| const VarDecl *D = cast<VarDecl>(getDecl(Name)); | ||
| return Prog.getPtrGlobal(*Prog.getGlobal(D)); | ||
| }; | ||
|
|
||
| const Pointer &GP = getGlobalPtr("func"); | ||
| const FunctionPointer &FP = GP.deref<FunctionPointer>(); | ||
| ASSERT_FALSE(FP.isZero()); | ||
| APValue A = FP.toAPValue(); | ||
| ASSERT_TRUE(A.hasValue()); | ||
| ASSERT_TRUE(A.isLValue()); | ||
| ASSERT_TRUE(A.hasLValuePath()); | ||
| const auto &Path = A.getLValuePath(); | ||
| ASSERT_EQ(Path.size(), 0u); | ||
| ASSERT_FALSE(A.getLValueBase().isNull()); | ||
| ASSERT_EQ(A.getLValueBase().dyn_cast<const ValueDecl *>(), getDecl("foo")); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| //===----------------------------------------------------------------------===// | ||
| // 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, c++20 | ||
|
|
||
| // template<> | ||
| // class bad_expected_access<void> : public exception { | ||
| // protected: | ||
| // bad_expected_access() noexcept; | ||
| // bad_expected_access(const bad_expected_access&) noexcept; | ||
| // bad_expected_access(bad_expected_access&&) noexcept; | ||
| // bad_expected_access& operator=(const bad_expected_access&) noexcept; | ||
| // bad_expected_access& operator=(bad_expected_access&&) noexcept; | ||
| // ~bad_expected_access(); | ||
| // | ||
| // public: | ||
| // const char* what() const noexcept override; | ||
| // }; | ||
|
|
||
| #include <cassert> | ||
| #include <exception> | ||
| #include <expected> | ||
| #include <type_traits> | ||
| #include <utility> | ||
|
|
||
| #include "test_macros.h" | ||
|
|
||
| struct Inherit : std::bad_expected_access<void> {}; | ||
|
|
||
| int main(int, char**) { | ||
| // base class | ||
| static_assert(std::is_base_of_v<std::exception, std::bad_expected_access<void>>); | ||
|
|
||
| // default constructor | ||
| { | ||
| Inherit exc; | ||
| ASSERT_NOEXCEPT(Inherit()); | ||
| } | ||
|
|
||
| // copy constructor | ||
| { | ||
| Inherit exc; | ||
| Inherit copy(exc); | ||
| ASSERT_NOEXCEPT(Inherit(exc)); | ||
| } | ||
|
|
||
| // move constructor | ||
| { | ||
| Inherit exc; | ||
| Inherit copy(std::move(exc)); | ||
| ASSERT_NOEXCEPT(Inherit(std::move(exc))); | ||
| } | ||
|
|
||
| // copy assignment | ||
| { | ||
| Inherit exc; | ||
| Inherit copy; | ||
| [[maybe_unused]] Inherit& result = (copy = exc); | ||
| ASSERT_NOEXCEPT(copy = exc); | ||
| } | ||
|
|
||
| // move assignment | ||
| { | ||
| Inherit exc; | ||
| Inherit copy; | ||
| [[maybe_unused]] Inherit& result = (copy = std::move(exc)); | ||
| ASSERT_NOEXCEPT(copy = std::move(exc)); | ||
| } | ||
|
|
||
| // what() | ||
| { | ||
| Inherit exc; | ||
| char const* what = exc.what(); | ||
| assert(what != nullptr); | ||
| ASSERT_NOEXCEPT(exc.what()); | ||
| } | ||
|
|
||
| return 0; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| //===----------------------------------------------------------------------===// | ||
| // 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, c++20 | ||
|
|
||
| // const char* what() const noexcept override; | ||
|
|
||
| #include <expected> | ||
| #include <cassert> | ||
| #include <utility> | ||
|
|
||
| #include "test_macros.h" | ||
|
|
||
| struct Foo {}; | ||
|
|
||
| int main(int, char**) { | ||
| { | ||
| std::bad_expected_access<int> const exc(99); | ||
| char const* what = exc.what(); | ||
| assert(what != nullptr); | ||
| ASSERT_NOEXCEPT(exc.what()); | ||
| } | ||
| { | ||
| std::bad_expected_access<Foo> const exc(Foo{}); | ||
| char const* what = exc.what(); | ||
| assert(what != nullptr); | ||
| ASSERT_NOEXCEPT(exc.what()); | ||
| } | ||
|
|
||
| return 0; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,381 @@ | ||
| //===----------------------------------------------------------------------===// | ||
| // | ||
| // 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, c++20 | ||
|
|
||
| // <functional> | ||
|
|
||
| // template<class F, class... Args> | ||
| // constexpr unspecified bind_back(F&& f, Args&&... args); | ||
|
|
||
| #include <functional> | ||
|
|
||
| #include <cassert> | ||
| #include <concepts> | ||
| #include <tuple> | ||
| #include <utility> | ||
|
|
||
| #include "callable_types.h" | ||
| #include "types.h" | ||
|
|
||
| constexpr void test_basic_bindings() { | ||
| { // 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>{})); | ||
| } | ||
| { | ||
| auto f = std::bind_back(MakeTuple{}, Elem<1>{}, Elem<2>{}, Elem<3>{}); | ||
| assert(f(Elem<10>{}, Elem<11>{}, Elem<12>{}) == | ||
| std::make_tuple(Elem<10>{}, Elem<11>{}, Elem<12>{}, Elem<1>{}, Elem<2>{}, Elem<3>{})); | ||
| } | ||
| } | ||
|
|
||
| { // Basic tests with fundamental types | ||
| int n = 2; | ||
| int m = 1; | ||
| int sum = 0; | ||
| auto add = [](int x, int y) { return x + y; }; | ||
| auto add_n = [](int a, int b, int c, int d, int e, int f) { return a + b + c + d + e + f; }; | ||
| auto add_ref = [&](int x, int y) -> int& { return sum = x + y; }; | ||
| auto add_rref = [&](int x, int y) -> int&& { return std::move(sum = x + y); }; | ||
|
|
||
| auto a = std::bind_back(add, m, n); | ||
| assert(a() == 3); | ||
|
|
||
| auto b = std::bind_back(add_n, m, n, m, m, m, m); | ||
| assert(b() == 7); | ||
|
|
||
| auto c = std::bind_back(add_n, n, m); | ||
| assert(c(1, 1, 1, 1) == 7); | ||
|
|
||
| auto d = std::bind_back(add_ref, n, m); | ||
| std::same_as<int&> decltype(auto) dresult(d()); | ||
| assert(dresult == 3); | ||
|
|
||
| auto e = std::bind_back(add_rref, n, m); | ||
| std::same_as<int&&> decltype(auto) eresult(e()); | ||
| assert(eresult == 3); | ||
|
|
||
| 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(add_n, 1, 1, 1); | ||
| assert(h(2, 2, 2) == 9); | ||
|
|
||
| auto i = std::bind_back(add_ref, n); | ||
| std::same_as<int&> decltype(auto) iresult(i(5)); | ||
| assert(iresult == 7); | ||
|
|
||
| auto j = std::bind_back(add_rref, m); | ||
| std::same_as<int&&> decltype(auto) jresult(j(4)); | ||
| assert(jresult == 5); | ||
| } | ||
| } | ||
|
|
||
| constexpr void test_edge_cases() { | ||
| { // Make sure we don't treat std::reference_wrapper specially. | ||
| auto sub = [](std::reference_wrapper<int> a, std::reference_wrapper<int> b) { return a.get() - b.get(); }; | ||
|
|
||
| int i = 1; | ||
| int j = 2; | ||
| auto f = std::bind_back(sub, std::ref(i)); | ||
| assert(f(std::ref(j)) == 1); | ||
| } | ||
|
|
||
| { // Make sure we can call a function that's a pointer to a member function. | ||
| struct MemberFunction { | ||
| constexpr int foo(int x, int y) { return x * y; } | ||
| }; | ||
|
|
||
| MemberFunction value; | ||
| auto fn = std::bind_back(&MemberFunction::foo, 2, 3); | ||
| assert(fn(value) == 6); | ||
| } | ||
|
|
||
| { // Make sure we can call a function that's a pointer to a member object. | ||
| struct MemberObject { | ||
| int obj; | ||
| }; | ||
|
|
||
| MemberObject value{.obj = 3}; | ||
| auto fn = std::bind_back(&MemberObject::obj); | ||
| assert(fn(value) == 3); | ||
| } | ||
| } | ||
|
|
||
| constexpr void test_passing_arguments() { | ||
| { // 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 f = std::bind_back(add, n, 1); | ||
| n = 100; | ||
| assert(f() == 3); | ||
| } | ||
|
|
||
| { // Make sure we pass the bound arguments to the function object | ||
| // with the right value category. | ||
| { | ||
| auto was_copied = [](CopyMoveInfo info) { return info.copy_kind == CopyMoveInfo::copy; }; | ||
| CopyMoveInfo info; | ||
| auto f = std::bind_back(was_copied, info); | ||
| assert(f()); | ||
| } | ||
|
|
||
| { | ||
| auto was_moved = [](CopyMoveInfo info) { return info.copy_kind == CopyMoveInfo::move; }; | ||
| CopyMoveInfo info; | ||
| auto f = std::bind_back(was_moved, info); | ||
| assert(std::move(f)()); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| constexpr void test_function_objects() { | ||
| { // Make sure we call the correctly cv-ref qualified operator() | ||
| // based on the value category of the bind_back unspecified-type. | ||
| struct X { | ||
| constexpr int operator()() & { return 1; } | ||
| constexpr int operator()() const& { return 2; } | ||
| constexpr int operator()() && { return 3; } | ||
| constexpr int operator()() const&& { return 4; } | ||
| }; | ||
|
|
||
| auto f = std::bind_back(X{}); | ||
| using F = decltype(f); | ||
| assert(static_cast<F&>(f)() == 1); | ||
| assert(static_cast<const F&>(f)() == 2); | ||
| assert(static_cast<F&&>(f)() == 3); | ||
| assert(static_cast<const F&&>(f)() == 4); | ||
| } | ||
|
|
||
| // Make sure the `bind_back` unspecified-type does not model 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 X { | ||
| void operator()() & = delete; | ||
| void operator()() const&; | ||
| void operator()() &&; | ||
| void operator()() const&&; | ||
| }; | ||
|
|
||
| using F = decltype(std::bind_back(X{})); | ||
| static_assert(!std::invocable<F&>); | ||
| static_assert(std::invocable<const F&>); | ||
| static_assert(std::invocable<F>); | ||
| static_assert(std::invocable<const F>); | ||
| } | ||
|
|
||
| // 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 X { | ||
| void operator()() &; | ||
| void operator()() const&; | ||
| void operator()() && = delete; | ||
| void operator()() const&&; | ||
| }; | ||
|
|
||
| using F = decltype(std::bind_back(X{})); | ||
| static_assert(std::invocable<F&>); | ||
| static_assert(std::invocable<const F&>); | ||
| static_assert(!std::invocable<F>); | ||
| static_assert(std::invocable<const F>); | ||
| } | ||
|
|
||
| { // Make sure we delete the const&& overload when the underlying call isn't valid. | ||
| struct X { | ||
| void operator()() &; | ||
| void operator()() const&; | ||
| void operator()() &&; | ||
| void operator()() const&& = delete; | ||
| }; | ||
|
|
||
| using F = decltype(std::bind_back(X{})); | ||
| static_assert(std::invocable<F&>); | ||
| static_assert(std::invocable<const F&>); | ||
| static_assert(std::invocable<F>); | ||
| static_assert(!std::invocable<const F>); | ||
| } | ||
| } | ||
|
|
||
| { // Extra value category tests | ||
| struct X {}; | ||
|
|
||
| { | ||
| struct Y { | ||
| void operator()(X&&) const&; | ||
| void operator()(X&&) && = delete; | ||
| }; | ||
|
|
||
| using F = decltype(std::bind_back(Y{})); | ||
| static_assert(std::invocable<F&, X>); | ||
| static_assert(!std::invocable<F, X>); | ||
| } | ||
|
|
||
| { | ||
| struct Y { | ||
| void operator()(const X&) const; | ||
| void operator()(X&&) const = delete; | ||
| }; | ||
|
|
||
| using F = decltype(std::bind_back(Y{}, X{})); | ||
| static_assert(std::invocable<F&>); | ||
| static_assert(!std::invocable<F>); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| constexpr void test_return_type() { | ||
| { // Test properties of the constructor of the unspecified-type returned by bind_back. | ||
| { // Test move constructor when function is move only. | ||
| MoveOnlyCallable<bool> value(true); | ||
| auto f = std::bind_back(std::move(value), 1); | ||
| assert(f()); | ||
| assert(f(1, 2, 3)); | ||
|
|
||
| auto f1 = std::move(f); | ||
| assert(!f()); | ||
| assert(f1()); | ||
| assert(f1(1, 2, 3)); | ||
|
|
||
| using F = decltype(f); | ||
| static_assert(std::is_move_constructible<F>::value); | ||
| static_assert(!std::is_copy_constructible<F>::value); | ||
| static_assert(!std::is_move_assignable<F>::value); | ||
| static_assert(!std::is_copy_assignable<F>::value); | ||
| } | ||
|
|
||
| { // Test move constructor when function is copyable but not assignable. | ||
| CopyCallable<bool> value(true); | ||
| auto f = std::bind_back(value, 1); | ||
| assert(f()); | ||
| assert(f(1, 2, 3)); | ||
|
|
||
| auto f1 = std::move(f); | ||
| assert(!f()); | ||
| assert(f1()); | ||
| assert(f1(1, 2, 3)); | ||
|
|
||
| auto f2 = std::bind_back(std::move(value), 1); | ||
| assert(f1()); | ||
| assert(f2()); | ||
| assert(f2(1, 2, 3)); | ||
|
|
||
| using F = decltype(f); | ||
| static_assert(std::is_move_constructible<F>::value); | ||
| static_assert(std::is_copy_constructible<F>::value); | ||
| static_assert(!std::is_move_assignable<F>::value); | ||
| static_assert(!std::is_copy_assignable<F>::value); | ||
| } | ||
|
|
||
| { // Test constructors when function is copy assignable. | ||
| using F = decltype(std::bind_back(std::declval<CopyAssignableWrapper&>(), 1)); | ||
| static_assert(std::is_move_constructible<F>::value); | ||
| static_assert(std::is_copy_constructible<F>::value); | ||
| static_assert(std::is_move_assignable<F>::value); | ||
| static_assert(std::is_copy_assignable<F>::value); | ||
| } | ||
|
|
||
| { // Test constructors when function is move assignable only. | ||
| using F = decltype(std::bind_back(std::declval<MoveAssignableWrapper>(), 1)); | ||
| static_assert(std::is_move_constructible<F>::value); | ||
| static_assert(!std::is_copy_constructible<F>::value); | ||
| static_assert(std::is_move_assignable<F>::value); | ||
| static_assert(!std::is_copy_assignable<F>::value); | ||
| } | ||
| } | ||
|
|
||
| { // Make sure bind_back's unspecified type's operator() is SFINAE-friendly. | ||
| using F = decltype(std::bind_back(std::declval<int (*)(int, int)>(), 1)); | ||
| static_assert(!std::is_invocable<F>::value); | ||
| static_assert(std::is_invocable<F, int>::value); | ||
| static_assert(!std::is_invocable<F, void*>::value); | ||
| static_assert(!std::is_invocable<F, int, int>::value); | ||
| } | ||
| } | ||
|
|
||
| constexpr bool test() { | ||
| test_basic_bindings(); | ||
| test_edge_cases(); | ||
| test_passing_arguments(); | ||
| test_function_objects(); | ||
| test_return_type(); | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| int main(int, char**) { | ||
| test(); | ||
| static_assert(test()); | ||
|
|
||
| return 0; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| //===----------------------------------------------------------------------===// | ||
| // | ||
| // 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, c++20 | ||
|
|
||
| // <functional> | ||
|
|
||
| // template<class F, class... Args> | ||
| // constexpr unspecified bind_back(F&& f, Args&&... args); | ||
|
|
||
| #include <functional> | ||
|
|
||
| #include "types.h" | ||
|
|
||
| constexpr int pass(int n) { return n; } | ||
|
|
||
| void test() { | ||
| { // Test calling constexpr function from non-constexpr `bind_back` result | ||
| auto f1 = std::bind_back(pass, 1); | ||
| static_assert(f1() == 1); // expected-error {{static assertion expression is not an integral constant expression}} | ||
| } | ||
|
|
||
| { // Test calling `bind_back` with template function | ||
| auto f1 = std::bind_back(do_nothing, 2); | ||
| // expected-error@-1 {{no matching function for call to 'bind_back'}} | ||
| } | ||
|
|
||
| { // Mandates: is_constructible_v<decay_t<F>, F> | ||
| struct F { | ||
| F() = default; | ||
| F(const F&) = default; | ||
| F(F&) = delete; | ||
|
|
||
| void operator()() {} | ||
| }; | ||
|
|
||
| F f; | ||
| auto f1 = std::bind_back(f); | ||
| // expected-error-re@*:* {{static assertion failed{{.*}}bind_back requires decay_t<F> to be constructible from F}} | ||
| } | ||
|
|
||
| { // Mandates: is_move_constructible_v<decay_t<F>> | ||
| struct F { | ||
| F() = default; | ||
| F(const F&) = default; | ||
| F(F&&) = delete; | ||
|
|
||
| void operator()() {} | ||
| }; | ||
|
|
||
| F f; | ||
| auto f1 = std::bind_back(f); | ||
| // expected-error-re@*:* {{static assertion failed{{.*}}bind_back requires decay_t<F> to be move constructible}} | ||
| } | ||
|
|
||
| { // Mandates: (is_constructible_v<decay_t<Args>, Args> && ...) | ||
| struct Arg { | ||
| Arg() = default; | ||
| Arg(const Arg&) = default; | ||
| Arg(Arg&) = delete; | ||
| }; | ||
|
|
||
| Arg x; | ||
| auto f = std::bind_back([](const Arg&) {}, x); | ||
| // expected-error-re@*:* {{static assertion failed{{.*}}bind_back requires all decay_t<Args> to be constructible from respective Args}} | ||
| // expected-error@*:* {{no matching constructor for initialization}} | ||
| } | ||
|
|
||
| { // Mandates: (is_move_constructible_v<decay_t<Args>> && ...) | ||
| struct Arg { | ||
| Arg() = default; | ||
| Arg(const Arg&) = default; | ||
| Arg(Arg&&) = delete; | ||
| }; | ||
|
|
||
| Arg x; | ||
| auto f = std::bind_back([](Arg&) {}, x); | ||
| // expected-error-re@*:* {{static assertion failed{{.*}}bind_back requires all decay_t<Args> to be move constructible}} | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| //===----------------------------------------------------------------------===// | ||
| // | ||
| // 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 TEST_STD_UTILITIES_FUNCTION_OBJECTS_FUNC_BIND_PARTIAL_TYPES_H | ||
| #define TEST_STD_UTILITIES_FUNCTION_OBJECTS_FUNC_BIND_PARTIAL_TYPES_H | ||
|
|
||
| #include <tuple> | ||
| #include <utility> | ||
|
|
||
| struct MakeTuple { | ||
| template <class... Args> | ||
| constexpr auto operator()(Args&&... args) const { | ||
| return std::make_tuple(std::forward<Args>(args)...); | ||
| } | ||
| }; | ||
|
|
||
| template <int X> | ||
| struct Elem { | ||
| template <int Y> | ||
| constexpr bool operator==(const Elem<Y>&) const { | ||
| return X == Y; | ||
| } | ||
| }; | ||
|
|
||
| struct CopyMoveInfo { | ||
| enum { none, copy, move } copy_kind; | ||
|
|
||
| constexpr CopyMoveInfo() : copy_kind(none) {} | ||
| constexpr CopyMoveInfo(const CopyMoveInfo&) : copy_kind(copy) {} | ||
| constexpr CopyMoveInfo(CopyMoveInfo&&) : copy_kind(move) {} | ||
| }; | ||
|
|
||
| template <class T> | ||
| T do_nothing(T t) { | ||
| return t; | ||
| } | ||
|
|
||
| #endif // TEST_STD_UTILITIES_FUNCTION_OBJECTS_FUNC_BIND_PARTIAL_TYPES_H |