-
Notifications
You must be signed in to change notification settings - Fork 86
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added a new boost/core/functor.hpp header.
The feader defines a new functor class template that can be used to wrap raw functions into a function object class. This is useful, for example, fo integrating std::unique_ptr and unique_resource with custom deleters implemented as raw functions (e.g. in C libraries).
- Loading branch information
Showing
6 changed files
with
285 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
[/ | ||
/ 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 <boost/core/functor.hpp>] | ||
|
||
[note This component requires a compiler supporting C++17 or newer.] | ||
|
||
The header `<boost/core/functor.hpp>` 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 > | ||
auto operator() (Args&&... args) const noexcept(...); | ||
}; | ||
|
||
} // namespace boost::core | ||
``` | ||
|
||
[endsect] | ||
|
||
[section `template< typename... Args > auto operator() (Args&&... args) const noexcept(...);`] | ||
|
||
* *Returns:* `Function(std::forward< Args >(args)...)`. | ||
* *Throws:* Nothing, unless invoking `Function` throws. | ||
|
||
[endsect] | ||
|
||
[endsect] | ||
|
||
[endsect] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)...))) | ||
{ | ||
return Function(static_cast< Args&& >(args)...); | ||
} | ||
}; | ||
|
||
} // namespace boost::core | ||
|
||
#endif // BOOST_CORE_FUNCTOR_HPP |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
/* | ||
* 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 <boost/config.hpp> | ||
|
||
#if !(defined(__cpp_nontype_template_parameter_auto) && __cpp_nontype_template_parameter_auto >= 201606l) && \ | ||
!(defined(BOOST_GCC_VERSION) && BOOST_GCC_VERSION >= 70100 && __cplusplus >= 201703l) && \ | ||
!(defined(BOOST_CLANG) && BOOST_CLANG_VERSION >= 40000 && __cplusplus >= 201406l) && \ | ||
!(defined(BOOST_MSVC) && BOOST_MSVC >= 1914 && BOOST_CXX_VERSION >= 201703l) | ||
//! Indicates whether the compiler does not support C++17 auto non-type template parameters | ||
//! TODO: Switch to a Boost.Config macro after https://github.com/boostorg/config/pull/492 is merged | ||
#define BOOST_CORE_NO_CXX17_AUTO_NONTYPE_TEMPLATE_PARAMS | ||
#endif | ||
|
||
#if !defined(BOOST_CORE_NO_CXX17_AUTO_NONTYPE_TEMPLATE_PARAMS) | ||
|
||
#include <boost/core/functor.hpp> | ||
#include <boost/core/lightweight_test.hpp> | ||
|
||
int g_n = 0; | ||
|
||
void void_func() | ||
{ | ||
++g_n; | ||
} | ||
|
||
int int_func() | ||
{ | ||
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_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< void_add1 > fun; | ||
fun(10); | ||
BOOST_TEST_EQ(g_n, 10); | ||
fun(20); | ||
BOOST_TEST_EQ(g_n, 30); | ||
} | ||
|
||
{ | ||
boost::core::functor< add2 > fun; | ||
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_CORE_NO_CXX17_AUTO_NONTYPE_TEMPLATE_PARAMS) | ||
|
||
#include <boost/config/pragma_message.hpp> | ||
|
||
BOOST_PRAGMA_MESSAGE("Test skipped because C++17 auto non-type template parameters are not supported") | ||
|
||
int main() | ||
{ | ||
return 0; | ||
} | ||
|
||
#endif // !defined(BOOST_CORE_NO_CXX17_AUTO_NONTYPE_TEMPLATE_PARAMS) |