Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 46 additions & 15 deletions include/boost/capy/ex/any_executor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@

#include <boost/capy/detail/config.hpp>
#include <boost/capy/continuation.hpp>
#include <boost/capy/detail/type_id.hpp>
#include <concepts>
#include <coroutine>
#include <memory>
#include <type_traits>
#include <typeinfo>

namespace boost {
namespace capy {
Expand Down Expand Up @@ -54,10 +54,10 @@ struct is_strand_type<strand<E>> : std::true_type {};
@par Default State

A default-constructed `any_executor` holds no executor.
`operator bool()`, `operator==`, and `target_type()` report the
empty state. `context()`, `on_work_started()`, `on_work_finished()`,
`dispatch()`, and `post()` are undefined behavior until an
executor is assigned.
`operator bool()`, `operator==`, and `target()` report the
empty state. `context()`, `on_work_started()`,
`on_work_finished()`, `dispatch()`, and `post()` are undefined
behavior until an executor is assigned.

@par Thread Safety

Expand Down Expand Up @@ -92,7 +92,8 @@ class any_executor
virtual std::coroutine_handle<> dispatch(continuation&) const = 0;
virtual void post(continuation&) const = 0;
virtual bool equals(impl_base const*) const noexcept = 0;
virtual std::type_info const& target_type() const noexcept = 0;
virtual detail::type_info const& target_type() const noexcept = 0;
virtual void const* target_ptr() const noexcept = 0;
};

template<class Ex>
Expand Down Expand Up @@ -138,9 +139,14 @@ class any_executor
return ex_ == static_cast<impl const*>(other)->ex_;
}

std::type_info const& target_type() const noexcept override
detail::type_info const& target_type() const noexcept override
{
return typeid(Ex);
return detail::type_id<Ex>();
}

void const* target_ptr() const noexcept override
{
return &ex_;
}
};

Expand Down Expand Up @@ -300,16 +306,41 @@ class any_executor
return p_->equals(other.p_.get());
}

/** Returns the type_info of the wrapped executor.
/** Return a pointer to the wrapped executor if it matches
the requested type.

Analogous to `std::any_cast<Ex>(&a)`. Top-level cv-qualifiers
on `Ex` are ignored when matching.

Copies of an `any_executor` share one stored executor, so
every copy returns the same address. A modification made
through the non-const overload is visible to all of them.
Such modifications are not synchronized with concurrent use
of any copy.

@tparam Ex The executor type to retrieve.

@return The `std::type_info` of the stored executor type,
or `typeid(void)` if empty.
@return A pointer to the stored executor, or `nullptr` if
this instance is empty or holds a different type.
*/
std::type_info const& target_type() const noexcept
template<class Ex>
Ex const* target() const noexcept
{
if(!p_ || p_->target_type() != detail::type_id<Ex>())
return nullptr;
// Casting from void const* avoids naming impl<Ex>, which would
// instantiate its overrides and reject non-executor types
return static_cast<Ex const*>(p_->target_ptr());
}

/// @copydoc target() const
template<class Ex>
Ex* target() noexcept
{
if(!p_)
return typeid(void);
return p_->target_type();
if(!p_ || p_->target_type() != detail::type_id<Ex>())
return nullptr;
return const_cast<Ex*>(
static_cast<Ex const*>(p_->target_ptr()));
}
};

Expand Down
19 changes: 10 additions & 9 deletions include/boost/capy/ex/executor_ref.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,10 @@ class executor_ref
public:
/** Construct a default instance.

Constructs an empty `executor_ref`. `operator bool()` and
`operator==()` report the empty state; `context()`,
`on_work_started()`, `on_work_finished()`, `dispatch()`,
`post()`, and `target()` are undefined behavior until an
Constructs an empty `executor_ref`. `operator bool()`,
`operator==()`, and `target()` report the empty state;
`context()`, `on_work_started()`, `on_work_finished()`,
`dispatch()`, and `post()` are undefined behavior until an
executor is assigned.
*/
executor_ref() = default;
Expand Down Expand Up @@ -265,21 +265,22 @@ class executor_ref
@tparam Executor The executor type to retrieve.

@return A pointer to the underlying executor, or
`nullptr` if the type does not match.
`nullptr` if this reference is empty or the type
does not match.
*/
template< typename Executor >
const Executor* target() const
const Executor* target() const noexcept
{
if ( *vt_->type_id == detail::type_id< Executor >() )
if ( vt_ && *vt_->type_id == detail::type_id< Executor >() )
return static_cast< Executor const* >( ex_ );
return nullptr;
}

/// @copydoc target() const
template< typename Executor>
Executor* target()
Executor* target() noexcept
{
if ( *vt_->type_id == detail::type_id< Executor >() )
if ( vt_ && *vt_->type_id == detail::type_id< Executor >() )
return const_cast< Executor* >(
static_cast< Executor const* >( ex_ ));
return nullptr;
Expand Down
2 changes: 2 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#
# Copyright (c) 2025 Mohammad Nejati
# Copyright (c) 2026 Michael Vandeberg
#
# 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)
Expand All @@ -13,4 +14,5 @@ if(NOT TARGET tests)
endif()

add_subdirectory(unit)
add_subdirectory(no_rtti)
add_subdirectory(doc)
2 changes: 2 additions & 0 deletions test/Jamfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#
# Copyright (c) 2025 Mohammad Nejati
# Copyright (c) 2026 Michael Vandeberg
#
# 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)
Expand All @@ -8,4 +9,5 @@
#

build-project unit ;
build-project no_rtti ;
build-project doc ;
26 changes: 26 additions & 0 deletions test/no_rtti/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#
# Copyright (c) 2026 Michael Vandeberg
#
# 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)
#
# Official repository: https://github.com/cppalliance/capy
#

# Built with RTTI disabled. The capy library it links is not, which is
# why any_executor.cpp may only type-erase executor types it defines.
add_executable(boost_capy_tests_no_rtti any_executor.cpp)
target_link_libraries(
boost_capy_tests_no_rtti PRIVATE
Boost::capy_test_suite_main
Boost::capy)

if(MSVC)
target_compile_options(boost_capy_tests_no_rtti PRIVATE /GR-)
else()
target_compile_options(boost_capy_tests_no_rtti PRIVATE -fno-rtti)
endif()

boost_capy_test_suite_discover_tests(boost_capy_tests_no_rtti)

add_dependencies(tests boost_capy_tests_no_rtti)
26 changes: 26 additions & 0 deletions test/no_rtti/Jamfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#
# Copyright (c) 2026 Michael Vandeberg
#
# 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)
#
# Official repository: https://github.com/cppalliance/capy
#

import ac ;
import testing ;

project
: requirements
$(c20-requires)
<library>/boost/capy//boost_capy
<source>../../extra/test_suite/test_main.cpp
<source>../../extra/test_suite/test_suite.cpp
<include>../../extra/test_suite
<rtti>off
<warnings>extra
<warnings-as-errors>on
<target-os>darwin,<undefined-sanitizer>norecover:<link>static
;

run any_executor.cpp : target-name no_rtti_any_executor ;
123 changes: 123 additions & 0 deletions test/no_rtti/any_executor.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
//
// Copyright (c) 2026 Michael Vandeberg
//
// 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)
//
// Official repository: https://github.com/cppalliance/capy
//

// Test that header file is self-contained.
#include <boost/capy/ex/any_executor.hpp>

#include <boost/capy/concept/executor.hpp>
#include <boost/capy/ex/executor_ref.hpp>

#include "test_suite.hpp"

/* This file is built with RTTI disabled (-fno-rtti, /GR-). It proves
that the type-erased executors identify types through detail::type_id
and not through typeid. The #error below fails the build if the flag
did not take effect, so a passing run cannot mean RTTI was on.

Only executor types local to this file are type-erased here. Under
CMake the capy library is built once, with RTTI on, and detail::type_id
has a different definition in each mode. Instantiating any_executor or
executor_ref for a library type such as thread_pool::executor_type in
this TU would therefore be an ODR violation against the library.
*/

#if !BOOST_CAPY_NO_RTTI
# error "test/no_rtti must be compiled with RTTI disabled"
#endif

namespace boost {
namespace capy {

namespace {

// N only makes distinct types. ctx is never dereferenced: context()
// is instantiated by the type erasure but never called, so no
// execution_context object (which lives in the library) is needed.
template<int N>
struct local_executor
{
int id = 0;
execution_context* ctx = nullptr;

bool operator==(local_executor const& other) const noexcept
{
return id == other.id;
}
execution_context& context() const noexcept { return *ctx; }
void on_work_started() const noexcept {}
void on_work_finished() const noexcept {}
std::coroutine_handle<> dispatch(continuation& c) const { return c.h; }
void post(continuation&) const {}
};

using exec_a = local_executor<0>;
using exec_b = local_executor<1>;

static_assert(Executor<exec_a>);
static_assert(Executor<any_executor>);

} // namespace

struct any_executor_no_rtti_test
{
void
testEquality()
{
any_executor a1(exec_a{1});
any_executor a1_again(exec_a{1});
any_executor a2(exec_a{2});
any_executor b1(exec_b{1});

BOOST_TEST(a1 == a1_again);
BOOST_TEST(!(a1 == a2));
// Equal ids, different types: equals() must reject on type
// before comparing values.
BOOST_TEST(!(a1 == b1));
}

void
testTarget()
{
any_executor empty;
BOOST_TEST(empty.target<exec_a>() == nullptr);

any_executor a(exec_a{7});
any_executor const& ca = a;
BOOST_TEST(a.target<exec_a>() != nullptr);
BOOST_TEST_EQ(a.target<exec_a>()->id, 7);
BOOST_TEST_EQ(ca.target<exec_a>()->id, 7);
BOOST_TEST(a.target<exec_a const>() == a.target<exec_a>());
BOOST_TEST(a.target<exec_b>() == nullptr);
BOOST_TEST(ca.target<exec_b>() == nullptr);
}

void
testExecutorRef()
{
exec_a ea{3};
executor_ref r(ea);
BOOST_TEST(r.target<exec_a>() == &ea);
BOOST_TEST(r.target<exec_b>() == nullptr);
}

void
run()
{
testEquality();
testTarget();
testExecutorRef();
}
};

TEST_SUITE(
any_executor_no_rtti_test,
"boost.capy.no_rtti.any_executor");

} // capy
} // boost
Loading
Loading