diff --git a/doc/changes.qbk b/doc/changes.qbk index aa14d51..3c9d600 100644 --- a/doc/changes.qbk +++ b/doc/changes.qbk @@ -1,12 +1,19 @@ [/ Copyright 2021 Peter Dimov - Copyright 2022-2023 Andrey Semashev + Copyright 2022-2024 Andrey Semashev Distributed under the Boost Software License, Version 1.0. https://boost.org/LICENSE_1_0.txt) ] [section Revision History] +[section Changes in 1.85.0] + +* Added a new [link core.functor `boost/core/functor.hpp`] header with a `functor` class template + for wrapping a raw function into a function object class. + +[endsect] + [section Changes in 1.84.0] * `boost::swap` utility function has been renamed to `boost::core::invoke_swap` to diff --git a/doc/core.qbk b/doc/core.qbk index 63d6ded..80e5fae 100644 --- a/doc/core.qbk +++ b/doc/core.qbk @@ -55,6 +55,7 @@ criteria for inclusion is that the utility component be: [include exchange.qbk] [include explicit_operator_bool.qbk] [include first_scalar.qbk] +[include functor.qbk] [include identity.qbk] [include ignore_unused.qbk] [include is_same.qbk] diff --git a/doc/functor.qbk b/doc/functor.qbk new file mode 100644 index 0000000..1dde742 --- /dev/null +++ b/doc/functor.qbk @@ -0,0 +1,101 @@ +[/ + / Copyright (c) 2024 Andrey Semashev + / + / Distributed under the Boost Software License, Version 1.0. (See accompanying + / file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + /] + +[section:functor functor] + +[simplesect Authors] + +* Andrey Semashev + +[endsimplesect] + +[section Header ] + +[note This component requires a compiler supporting C++17 or newer.] + +The header `` defines the `boost::core::functor` class template +that wraps a raw function specified in its template parameter into a function object class. +The function object forwards any arguments passed to it to the wrapped function and returns +the result of the call. + +The `functor` wrapper can be useful in cases when a function object class type is required, +for example, for use with smart pointers such as `std::unique_ptr`, where the actual logic +of the function object is already implemented as a raw function, possibly provided by a +third party library. Since `functor` is default-constructible and does not store a pointer +to the wrapped function internally, using `functor` is less error-prone and more efficient +than using the pointer to function instead. For example, with `std::unique_ptr` you don't +need to pass a pointer to the deleter function in the `std::unique_ptr` constructor, and +the `std::unique_ptr` object does not store and invoke a pointer to the deleter function. +With `functor`, the deleter function becomes part of the `std::unique_ptr` type, which +prevents mixing pointers with incompatible deleters. + +``` +void my_deleter(void* p); + +using malloc_ptr = std::unique_ptr< char, boost::core::functor< std::free > >; +using my_ptr = std::unique_ptr< char, boost::core::functor< my_deleter > >; + +my_ptr create_string(std::size_t size); +void consume_string(my_ptr&& str); + +malloc_ptr ptr1(static_cast< char* >(std::malloc(size))); +// ptr1 = allocate_string(size); // error, cannot convert my_ptr to malloc_ptr +my_ptr ptr2 = create_string(size); // ok + +// consume_string(std::move(ptr1)); // error, cannot convert malloc_ptr&& to my_ptr +consume_string(std::move(ptr2)); // ok +``` + +Using `functor` may also be beneficial for reducing generated code sizes. For example, in +order to avoid storing and invoking a pointer to the deleter function in `std::shared_ptr`, +one may be inclined to use lambda functions to wrap the deleter function call like this: + +``` +std::shared_ptr< int > ptr(static_cast< int* >(std::malloc(sizeof(int))), [](int* p) { std::free(p); }); +``` + +The problem is that every lambda function declaration introduces a unique type, even if +the lambda function definition matches exactly one of the previously declared lambda +functions. Thus, if `std::shared_ptr` objects like the one above are created in multiple +places in the program, the definition of the shared pointer counter and associated code +and data (e.g. virtual function table) will be duplicated for each instance. + +Replacing the lambda function with `functor` solves this problem without sacrificing +readability or efficiency: + +``` +std::shared_ptr< int > ptr(static_cast< int* >(std::malloc(sizeof(int))), boost::core::functor< std::free >()); +``` + +[section Synopsis] + +``` +namespace boost::core { + +template< auto Function > +struct functor +{ + template< typename... Args > + decltype(auto) operator() (Args&&... args) const noexcept(...); +}; + +} // namespace boost::core +``` + +[endsect] + +[section `template< typename... Args > decltype(auto) operator() (Args&&... args) const noexcept(...);`] + +* *Effects:* `return Function(std::forward< Args >(args)...)`. +* *Throws:* Nothing, unless invoking `Function` throws. +* *Note:* This function only participates in overload resolution if `Function(std::forward< Args >(args)...)` is a valid call expression. + +[endsect] + +[endsect] + +[endsect] diff --git a/include/boost/core/functor.hpp b/include/boost/core/functor.hpp new file mode 100644 index 0000000..441f940 --- /dev/null +++ b/include/boost/core/functor.hpp @@ -0,0 +1,34 @@ +/* + * Copyright Andrey Semashev 2024. + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + */ +/*! + * \file functor.hpp + * \author Andrey Semashev + * \date 2024-01-23 + * + * This header contains a \c functor implementation. This is a function object + * that invokes a function that is specified as its template parameter. + */ + +#ifndef BOOST_CORE_FUNCTOR_HPP +#define BOOST_CORE_FUNCTOR_HPP + +namespace boost::core { + +//! A function object that invokes a function specified as its template parameter +template< auto Function > +struct functor +{ + template< typename... Args > + auto operator() (Args&&... args) const noexcept(noexcept(Function(static_cast< Args&& >(args)...))) -> decltype(Function(static_cast< Args&& >(args)...)) + { + return Function(static_cast< Args&& >(args)...); + } +}; + +} // namespace boost::core + +#endif // BOOST_CORE_FUNCTOR_HPP diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 11fc013..b64a094 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -189,6 +189,8 @@ run underlying_type.cpp ; run fclose_deleter_test.cpp : : : windows:_CRT_SECURE_NO_WARNINGS windows:_CRT_SECURE_NO_DEPRECATE ; +run functor_test.cpp ; + run pointer_traits_pointer_test.cpp ; run pointer_traits_element_type_test.cpp ; run pointer_traits_difference_type_test.cpp ; diff --git a/test/functor_test.cpp b/test/functor_test.cpp new file mode 100644 index 0000000..873e5b0 --- /dev/null +++ b/test/functor_test.cpp @@ -0,0 +1,202 @@ +/* + * Copyright Andrey Semashev 2024. + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + */ +/*! + * \file functor_test.cpp + * \author Andrey Semashev + * \date 2024-01-23 + * + * This file contains tests for \c boost::core::functor. + */ + +#include + +#if !defined(BOOST_NO_CXX17_AUTO_NONTYPE_TEMPLATE_PARAMS) + +#include +#include +#include +#include + +#if (defined(__cpp_lib_is_invocable) && (__cpp_lib_is_invocable >= 201703l)) || \ + (defined(BOOST_MSSTL_VERSION) && (BOOST_MSSTL_VERSION >= 140) && (BOOST_CXX_VERSION >= 201703l)) + +namespace test { + +using std::is_invocable; + +} // namespace test + +#else + +namespace test { + +// A simplified implementation that does not support member function pointers +template< typename Func, typename... Args > +struct is_invocable_impl +{ + template< typename F = Func, typename = decltype(std::declval< F >()(std::declval< Args >()...)) > + static std::true_type _check_invocable(int); + static std::false_type _check_invocable(...); + + typedef decltype(is_invocable_impl::_check_invocable(0)) type; +}; + +template< typename Func, typename... Args > +struct is_invocable : public is_invocable_impl< Func, Args... >::type { }; + +} // namespace test + +#endif + +int g_n = 0; + +void void_func() +{ + ++g_n; +} + +int int_func() +{ + return ++g_n; +} + +int& int_ref_func() +{ + ++g_n; + return g_n; +} + +void void_add1(int x) +{ + g_n += x; +} + +int add2(int x, int y) +{ + return x + y; +} + +namespace test_ns { + +int add3(int x, int y, int z) +{ + return x + y + z; +} + +} // namespace test_ns + +int int_func_noexcept() noexcept +{ + return ++g_n; +} + +int main() +{ + { + boost::core::functor< void_func > fun; + BOOST_TEST_TRAIT_TRUE((test::is_invocable< boost::core::functor< void_func >& >)); + BOOST_TEST_TRAIT_TRUE((test::is_invocable< boost::core::functor< void_func > const& >)); + BOOST_TEST_TRAIT_FALSE((test::is_invocable< boost::core::functor< void_func > const&, int >)); + BOOST_TEST_EQ(noexcept(fun()), false); + fun(); + BOOST_TEST_EQ(g_n, 1); + fun(); + BOOST_TEST_EQ(g_n, 2); + } + + g_n = 0; + { + boost::core::functor< int_func > fun; + int res = fun(); + BOOST_TEST_EQ(res, 1); + BOOST_TEST_EQ(g_n, 1); + res = fun(); + BOOST_TEST_EQ(res, 2); + BOOST_TEST_EQ(g_n, 2); + } + + g_n = 0; + { + boost::core::functor< int_ref_func > fun; + int& res1 = fun(); + BOOST_TEST_EQ(&res1, &g_n); + BOOST_TEST_EQ(res1, 1); + int& res2 = fun(); + BOOST_TEST_EQ(&res2, &g_n); + BOOST_TEST_EQ(res2, 2); + } + + g_n = 0; + { + boost::core::functor< void_add1 > fun; + BOOST_TEST_TRAIT_FALSE((test::is_invocable< boost::core::functor< void_add1 >& >)); + BOOST_TEST_TRAIT_FALSE((test::is_invocable< boost::core::functor< void_add1 > const& >)); + BOOST_TEST_TRAIT_TRUE((test::is_invocable< boost::core::functor< void_add1 >&, int >)); + BOOST_TEST_TRAIT_TRUE((test::is_invocable< boost::core::functor< void_add1 > const&, int >)); + BOOST_TEST_TRAIT_TRUE((test::is_invocable< boost::core::functor< void_add1 >&, short int >)); + BOOST_TEST_TRAIT_TRUE((test::is_invocable< boost::core::functor< void_add1 > const&, short int >)); + BOOST_TEST_TRAIT_FALSE((test::is_invocable< boost::core::functor< void_add1 > const&, int, int >)); + BOOST_TEST_TRAIT_FALSE((test::is_invocable< boost::core::functor< void_add1 > const&, const char* >)); + fun(10); + BOOST_TEST_EQ(g_n, 10); + fun(20); + BOOST_TEST_EQ(g_n, 30); + } + + { + boost::core::functor< add2 > fun; + BOOST_TEST_TRAIT_FALSE((test::is_invocable< boost::core::functor< add2 >& >)); + BOOST_TEST_TRAIT_FALSE((test::is_invocable< boost::core::functor< add2 > const& >)); + BOOST_TEST_TRAIT_FALSE((test::is_invocable< boost::core::functor< add2 >&, int >)); + BOOST_TEST_TRAIT_FALSE((test::is_invocable< boost::core::functor< add2 > const&, int >)); + BOOST_TEST_TRAIT_TRUE((test::is_invocable< boost::core::functor< add2 >&, int, int >)); + BOOST_TEST_TRAIT_TRUE((test::is_invocable< boost::core::functor< add2 > const&, int, int >)); + BOOST_TEST_TRAIT_TRUE((test::is_invocable< boost::core::functor< add2 >&, short int, signed char >)); + BOOST_TEST_TRAIT_TRUE((test::is_invocable< boost::core::functor< add2 > const&, short int, signed char >)); + BOOST_TEST_TRAIT_FALSE((test::is_invocable< boost::core::functor< add2 > const&, const char* >)); + BOOST_TEST_TRAIT_FALSE((test::is_invocable< boost::core::functor< add2 > const&, const char*, float >)); + int res = fun(10, 20); + BOOST_TEST_EQ(res, 30); + res = fun(30, 40); + BOOST_TEST_EQ(res, 70); + } + + { + boost::core::functor< test_ns::add3 > fun; + int res = fun(10, 20, 30); + BOOST_TEST_EQ(res, 60); + res = fun(40, 50, 60); + BOOST_TEST_EQ(res, 150); + } + + g_n = 0; + { + boost::core::functor< int_func_noexcept > fun; + BOOST_TEST_EQ(noexcept(fun()), true); + int res = fun(); + BOOST_TEST_EQ(res, 1); + BOOST_TEST_EQ(g_n, 1); + res = fun(); + BOOST_TEST_EQ(res, 2); + BOOST_TEST_EQ(g_n, 2); + } + + return boost::report_errors(); +} + +#else // !defined(BOOST_NO_CXX17_AUTO_NONTYPE_TEMPLATE_PARAMS) + +#include + +BOOST_PRAGMA_MESSAGE("Test skipped because C++17 auto non-type template parameters are not supported") + +int main() +{ + return 0; +} + +#endif // !defined(BOOST_NO_CXX17_AUTO_NONTYPE_TEMPLATE_PARAMS)