diff --git a/BUILD b/BUILD index 45093907505..9a1cbf3a043 100644 --- a/BUILD +++ b/BUILD @@ -712,6 +712,14 @@ grpc_cc_library( ], ) +grpc_cc_library( + name = "capture", + external_deps = ["absl/utility"], + language = "c++", + public_hdrs = ["src/core/lib/gprpp/capture.h"], + deps = ["gpr_platform"], +) + grpc_cc_library( name = "construct_destruct", language = "c++", diff --git a/CMakeLists.txt b/CMakeLists.txt index e9c820e5a14..f308c097472 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -817,6 +817,7 @@ if(gRPC_BUILD_TESTS) add_dependencies(buildtests_cxx byte_buffer_test) add_dependencies(buildtests_cxx byte_stream_test) add_dependencies(buildtests_cxx cancel_ares_query_test) + add_dependencies(buildtests_cxx capture_test) add_dependencies(buildtests_cxx cel_authorization_engine_test) add_dependencies(buildtests_cxx certificate_provider_registry_test) add_dependencies(buildtests_cxx certificate_provider_store_test) @@ -9287,6 +9288,41 @@ target_link_libraries(cancel_ares_query_test ) +endif() +if(gRPC_BUILD_TESTS) + +add_executable(capture_test + test/core/gprpp/capture_test.cc + third_party/googletest/googletest/src/gtest-all.cc + third_party/googletest/googlemock/src/gmock-all.cc +) + +target_include_directories(capture_test + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${_gRPC_ADDRESS_SORTING_INCLUDE_DIR} + ${_gRPC_RE2_INCLUDE_DIR} + ${_gRPC_SSL_INCLUDE_DIR} + ${_gRPC_UPB_GENERATED_DIR} + ${_gRPC_UPB_GRPC_GENERATED_DIR} + ${_gRPC_UPB_INCLUDE_DIR} + ${_gRPC_XXHASH_INCLUDE_DIR} + ${_gRPC_ZLIB_INCLUDE_DIR} + third_party/googletest/googletest/include + third_party/googletest/googletest + third_party/googletest/googlemock/include + third_party/googletest/googlemock + ${_gRPC_PROTO_GENS_DIR} +) + +target_link_libraries(capture_test + ${_gRPC_PROTOBUF_LIBRARIES} + ${_gRPC_ALLTARGETS_LIBRARIES} + absl::utility +) + + endif() if(gRPC_BUILD_TESTS) diff --git a/build_autogenerated.yaml b/build_autogenerated.yaml index 376b6187e71..0d111db6af9 100644 --- a/build_autogenerated.yaml +++ b/build_autogenerated.yaml @@ -4778,6 +4778,17 @@ targets: deps: - grpc++_test_config - grpc++_test_util +- name: capture_test + gtest: true + build: test + language: c++ + headers: + - src/core/lib/gprpp/capture.h + src: + - test/core/gprpp/capture_test.cc + deps: + - absl/utility:utility + uses_polling: false - name: cel_authorization_engine_test gtest: true build: test diff --git a/src/core/lib/gprpp/capture.h b/src/core/lib/gprpp/capture.h new file mode 100644 index 00000000000..2ab3e1f20d5 --- /dev/null +++ b/src/core/lib/gprpp/capture.h @@ -0,0 +1,75 @@ +// Copyright 2021 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef GRPC_CORE_LIB_GPRPP_CAPTURE_H +#define GRPC_CORE_LIB_GPRPP_CAPTURE_H + +#include + +#include +#include +#include "absl/utility/utility.h" + +namespace grpc_core { + +namespace detail { + +template +class Capture { + public: + explicit Capture(F f, Captures... captures) + : f_(std::move(f)), captures_(std::move(captures)...) {} + + template + decltype(std::declval()(static_cast(nullptr)..., + std::declval()...)) + operator()(Args... args) { + auto f = &f_; + return absl::apply( + [f, &args...](Captures&... captures) { + return (*f)(&captures..., std::move(args)...); + }, + captures_); + } + + private: + GPR_NO_UNIQUE_ADDRESS F f_; + GPR_NO_UNIQUE_ADDRESS std::tuple captures_; +}; + +} // namespace detail + +// C++11 helper - best explained by usage: +// +// BigThing big_thing; +// auto f = Capture( +// [](BigThing* c, int a, int b) { /*...*/ }, +// std::move(big_thing)); +// +// results in: f being a callable that takes arguments (int a, int b), and +// captures the original value of big_thing by move. Each call, a pointer to +// each captured thing is inserted into the argument list at the beginning so it +// can be manipulated. +// +// Captured values are mutable, and it's the users responsibility to ensure, +// should this callable be invoked from different threads, that proper locking +// is implemented. +template +detail::Capture Capture(F f, Captures... captures) { + return detail::Capture(std::move(f), std::move(captures)...); +} + +} // namespace grpc_core + +#endif // GRPC_CORE_LIB_GPRPP_CAPTURE_H diff --git a/test/core/gprpp/BUILD b/test/core/gprpp/BUILD index 7408a17199d..54013578633 100644 --- a/test/core/gprpp/BUILD +++ b/test/core/gprpp/BUILD @@ -271,3 +271,15 @@ grpc_cc_test( "//test/core/util:grpc_test_util", ], ) + +grpc_cc_test( + name = "capture_test", + srcs = ["capture_test.cc"], + external_deps = ["gtest"], + language = "c++", + uses_polling = False, + deps = [ + "//:capture", + "//test/core/util:grpc_suppressions", + ], +) diff --git a/test/core/gprpp/capture_test.cc b/test/core/gprpp/capture_test.cc new file mode 100644 index 00000000000..e45c738ff4e --- /dev/null +++ b/test/core/gprpp/capture_test.cc @@ -0,0 +1,38 @@ +// Copyright 2021 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "src/core/lib/gprpp/capture.h" +#include + +namespace grpc_core { + +TEST(CaptureTest, Capture) { + auto f = Capture([](int* p) { EXPECT_EQ(*p, 42); }, 42); + f(); +} + +TEST(CaptureTest, WithArgsAndReturn) { + int captured = 1; + auto f = + Capture([captured](int* p, int arg) { return (captured + *p) * arg; }, 2); + EXPECT_EQ(f(2), 6); + EXPECT_EQ(f(3), 9); +} + +} // namespace grpc_core + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tools/run_tests/generated/tests.json b/tools/run_tests/generated/tests.json index 52cb718d201..68da383d383 100644 --- a/tools/run_tests/generated/tests.json +++ b/tools/run_tests/generated/tests.json @@ -3889,6 +3889,30 @@ ], "uses_polling": true }, + { + "args": [], + "benchmark": false, + "ci_platforms": [ + "linux", + "mac", + "posix", + "windows" + ], + "cpu_cost": 1.0, + "exclude_configs": [], + "exclude_iomgrs": [], + "flaky": false, + "gtest": true, + "language": "c++", + "name": "capture_test", + "platforms": [ + "linux", + "mac", + "posix", + "windows" + ], + "uses_polling": false + }, { "args": [], "benchmark": false,