Skip to content

Commit

Permalink
Added a new boost/core/functor.hpp header.
Browse files Browse the repository at this point in the history
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
Lastique committed Jan 23, 2024
1 parent 6f70ee8 commit 8439ef6
Show file tree
Hide file tree
Showing 6 changed files with 285 additions and 1 deletion.
9 changes: 8 additions & 1 deletion doc/changes.qbk
Original file line number Diff line number Diff line change
@@ -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
Expand Down
1 change: 1 addition & 0 deletions doc/core.qbk
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
100 changes: 100 additions & 0 deletions doc/functor.qbk
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]
34 changes: 34 additions & 0 deletions include/boost/core/functor.hpp
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
2 changes: 2 additions & 0 deletions test/Jamfile.v2
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,8 @@ run underlying_type.cpp ;

run fclose_deleter_test.cpp : : : <target-os>windows:<define>_CRT_SECURE_NO_WARNINGS <target-os>windows:<define>_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 ;
Expand Down
140 changes: 140 additions & 0 deletions test/functor_test.cpp
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)

0 comments on commit 8439ef6

Please sign in to comment.