Add future.h (#1618)

This commit is contained in:
Roland Reichwein 2026-10-13 12:42:15 +02:00 committed by GitHub
parent 41bfe65dda
commit d87cbd37f4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 1516 additions and 1 deletions

View File

@ -0,0 +1,247 @@
---
title: "future / promise"
---
{{< callout type="info">}}
Header: `future.h`
From: `20.50.0`
{{< /callout >}}
A lightweight, heap-free `future` / `promise` pair for passing a single result from a producer (e.g. an ISR, a
thread or an RPC response handler) to a consumer.
Unlike `std::future`/`std::promise`, the shared state is **not** allocated. It is an `etl::shared_state<T>` object
whose storage and lifetime are managed by the user, for example as an element of a fixed-size pending-request table.
`etl::future<T>` and `etl::promise<T>` are non-owning handles to it.
```cpp
template <typename T>
class shared_state;
template <typename T>
class future;
template <typename T>
class promise;
```
All three are specialised for `void`, where there is no value, only a *ready* notification, and for references
(`T&`), where a pointer to the referenced object is stored. `T` must not be an array type.
**Differences from the standard library**
- `etl::future<T>` has the semantics of `std::shared_future<T>`: it is copyable, and `get()` does not consume the
value, so it may be called more than once, by every copy. `get()` returns `const T&` (`R&` for `future<R&>`).
- `etl::promise<T>` is, like `std::promise<T>`, move-only (C++11) or non-copyable (C++03).
- `get()` does not block. Poll `is_ready()` or use an external notification.
- There is no `set_exception()` and no *broken promise* notification. A consumer cannot distinguish a slow producer
from one that has gone away.
**Availability**
The header requires `etl::atomic_bool`. The classes are only defined when `ETL_HAS_ATOMIC` is `1`
(see `etl::atomic`).
**Thread safety**
`set_value()` and `is_ready()`/`get()` may be called from different threads or contexts, provided that at most
**one** context calls `set_value()` and at most **one** context calls `get()`. The ready flag uses
release/acquire ordering, so a value written by `set_value()` is visible after `is_ready()` returns `true`.
`reset()` must not be called concurrently with any other member.
## shared_state
Holds the result and an atomic *ready* flag. Non-copyable.
```cpp
shared_state()
```
**Description**
Constructs a state that is not ready.
---
```cpp
~shared_state()
```
**Description**
Destroys the stored value, if one has been set.
---
```cpp
void set_value(const T& value)
void set_value(T&& value) // C++11
void set_value(T& value) // shared_state<T&> only
void set_value() // shared_state<void> only
```
**Description**
Stores the value (or, for `shared_state<T&>`, the address of the referenced object) and marks the state as ready.
Asserts `etl::promise_already_satisfied` if the state is already ready. Call `reset()` first to reuse the state.
---
```cpp
bool is_ready() const noexcept
```
**Description**
Returns `true` if a value has been set.
---
```cpp
T& get()
const T& get() const
T& get() const // shared_state<T&> only
```
**Description**
Returns a reference to the stored value.
Not available for `shared_state<void>`.
Asserts `etl::future_not_ready` if the state is not ready.
---
```cpp
void reset()
```
**Description**
Destroys the stored value, if any, and marks the state as not ready, so that it can be reused.
## future
A non-owning handle to a `shared_state`. Copyable, with the semantics of `std::shared_future`.
```cpp
future() noexcept
```
**Description**
Constructs a future that is not associated with a shared state. `valid()` returns `false`.
---
```cpp
explicit future(etl::shared_state<T>& state) noexcept
```
**Description**
Constructs a future associated with `state`.
---
```cpp
bool valid() const noexcept
```
**Description**
Returns `true` if the future is associated with a shared state.
---
```cpp
bool is_ready() const noexcept
```
**Description**
Returns `true` if the future is valid and the result is available.
---
```cpp
const T& get() const
R& get() const // future<R&> only
void get() const // future<void> only
```
**Description**
Returns the result.
Asserts `etl::future_no_state` if the future is not valid (default constructed, no shared state).
Asserts `etl::future_not_ready` if the future is valid but the result is not yet available.
Does not block. Poll `is_ready()` or use an external notification first.
The value is not moved out and the future remains valid, so `get()` may be called more than once.
## promise
A non-owning handle to a `shared_state` that fulfils it. Move-only (C++11) or non-copyable (C++03), so that there is
only one producer.
```cpp
promise() noexcept
```
**Description**
Constructs a promise that is not associated with a shared state. `valid()` returns `false`.
---
```cpp
explicit promise(etl::shared_state<T>& state) noexcept
```
**Description**
Constructs a promise associated with `state`.
---
```cpp
promise(promise&& other) noexcept // C++11
promise& operator=(promise&& other) noexcept // C++11
```
**Description**
Transfers the association with the shared state. `other` is no longer valid.
---
```cpp
bool valid() const noexcept
```
**Description**
Returns `true` if the promise is associated with a shared state.
---
```cpp
etl::future<T> get_future()
```
**Description**
Returns a future associated with the same shared state.
May be called more than once. All futures refer to the same state.
Asserts `etl::future_no_state` if the promise is not valid.
---
```cpp
void set_value(const T& value)
void set_value(T&& value) // C++11
void set_value(R& value) // promise<R&> only
void set_value() // promise<void> only
```
**Description**
Fulfils the promise.
Asserts `etl::future_no_state` if the promise is not valid.
Asserts `etl::promise_already_satisfied` if the shared state is already ready.
## Errors
| Exception | Reason | Error text |
| -------------------------------- | -------------------------------------------------------- | --------------------------- |
| `etl::future_exception` | Base of all future/promise exceptions. | |
| `etl::future_not_ready` | `get()` on a future or state that is not ready. | `future:not ready` |
| `etl::promise_already_satisfied` | `set_value()` on a state that is already ready. | `promise:already satisfied` |
| `etl::future_no_state` | Use of a `future` or `promise` that has no shared state. | `future:no state` |
`etl::future_exception` is derived from `etl::exception`.
## Example
```cpp
#include "etl/future.h"
etl::shared_state<int> state; // Owned by the application.
// Consumer
etl::promise<int> promise(state);
etl::future<int> future = promise.get_future();
// Producer (e.g. in an ISR or another thread)
promise.set_value(42);
// Consumer
if (future.is_ready())
{
int result = future.get(); // 42
}
// Reuse the state for the next request.
state.reset();
```

View File

@ -112,5 +112,6 @@ SOFTWARE.
#define ETL_FORMAT_FILE_ID "79"
#define ETL_INPLACE_FUNCTION_FILE_ID "80"
#define ETL_INTRUSIVE_AVL_TREE_FILE_ID "81"
#define ETL_CYCLIC_VALUE_FILE_ID "82"
#define ETL_FUTURE_FILE_ID "82"
#define ETL_CYCLIC_VALUE_FILE_ID "83"
#endif

603
include/etl/future.h Normal file
View File

@ -0,0 +1,603 @@
///\file
/******************************************************************************
The MIT License(MIT)
Embedded Template Library.
https://github.com/ETLCPP/etl
https://www.etlcpp.com
Copyright(c) 2026 BMW AG
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files(the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions :
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
******************************************************************************/
#ifndef ETL_FUTURE_INCLUDED
#define ETL_FUTURE_INCLUDED
#include "platform.h"
#include "alignment.h"
#include "atomic.h"
#include "error_handler.h"
#include "exception.h"
#include "file_error_numbers.h"
#include "memory.h"
#include "nullptr.h"
#include "placement_new.h"
#include "static_assert.h"
#include "type_traits.h"
#include "utility.h"
#if ETL_HAS_ATOMIC
namespace etl
{
//***************************************************************************
/// Exception types for future/promise.
//***************************************************************************
class future_exception : public etl::exception
{
public:
future_exception(string_type reason_, string_type file_name_, numeric_type line_number_)
: exception(reason_, file_name_, line_number_)
{
}
};
class future_not_ready : public etl::future_exception
{
public:
future_not_ready(string_type file_name_, numeric_type line_number_)
: future_exception(ETL_ERROR_TEXT("future:not ready", ETL_FUTURE_FILE_ID"A"), file_name_, line_number_)
{
}
};
class promise_already_satisfied : public etl::future_exception
{
public:
promise_already_satisfied(string_type file_name_, numeric_type line_number_)
: future_exception(ETL_ERROR_TEXT("promise:already satisfied", ETL_FUTURE_FILE_ID"B"), file_name_, line_number_)
{
}
};
class future_no_state : public etl::future_exception
{
public:
future_no_state(string_type file_name_, numeric_type line_number_)
: future_exception(ETL_ERROR_TEXT("future:no state", ETL_FUTURE_FILE_ID"C"), file_name_, line_number_)
{
}
};
//***************************************************************************
/// Shared state between a promise and a future.
/// No heap allocation. The storage and lifetime of the shared_state are
/// managed by the user, e.g. as an element of a fixed-size table.
///
/// Thread safety: set_value() and is_ready()/get() may be called from
/// different threads provided that at most ONE thread calls set_value()
/// and at most ONE thread calls get(). The atomic ready flag provides
/// acquire/release ordering.
//***************************************************************************
template <typename T>
class shared_state
{
public:
ETL_STATIC_ASSERT(!etl::is_array<T>::value, "etl::shared_state: T must not be an array type");
shared_state()
: ready_(false)
{
}
~shared_state()
{
if (is_ready())
{
ptr()->~T();
}
}
//*************************************************************************
/// Store the result value and mark the state as ready.
//*************************************************************************
void set_value(const T& value)
{
ETL_ASSERT(!is_ready(), ETL_ERROR(promise_already_satisfied));
::new (static_cast<void*>(&storage_)) T(value);
ready_.store(true, etl::memory_order_release);
}
#if ETL_USING_CPP11
//*************************************************************************
/// Store the result value (move) and mark the state as ready.
//*************************************************************************
void set_value(T&& value)
{
ETL_ASSERT(!is_ready(), ETL_ERROR(promise_already_satisfied));
::new (static_cast<void*>(&storage_)) T(etl::move(value));
ready_.store(true, etl::memory_order_release);
}
#endif
//*************************************************************************
/// Check whether the result is available.
//*************************************************************************
ETL_NODISCARD
bool is_ready() const ETL_NOEXCEPT
{
return ready_.load(etl::memory_order_acquire);
}
//*************************************************************************
/// Get the stored value.
/// Asserts etl::future_not_ready if the state is not ready.
//*************************************************************************
ETL_NODISCARD
T& get()
{
ETL_ASSERT(is_ready(), ETL_ERROR(future_not_ready));
return *ptr();
}
ETL_NODISCARD
const T& get() const
{
ETL_ASSERT(is_ready(), ETL_ERROR(future_not_ready));
return *ptr();
}
//*************************************************************************
/// Reset for reuse.
//*************************************************************************
void reset()
{
if (is_ready())
{
ptr()->~T();
ready_.store(false, etl::memory_order_release);
}
}
private:
//*************************************************************************
/// Pointer to the object in the storage.
/// Laundered, as the storage is reused after reset().
//*************************************************************************
T* ptr()
{
return etl::launder(reinterpret_cast<T*>(&storage_));
}
const T* ptr() const
{
return etl::launder(reinterpret_cast<const T*>(&storage_));
}
// Non-copyable.
shared_state(const shared_state&) ETL_DELETE;
shared_state& operator=(const shared_state&) ETL_DELETE;
typename etl::aligned_storage<sizeof(T), etl::alignment_of<T>::value>::type storage_;
etl::atomic_bool ready_;
};
//***************************************************************************
/// Specialisation for references — stores a pointer to the referenced object.
//***************************************************************************
template <typename T>
class shared_state<T&>
{
public:
shared_state()
: p_value_(ETL_NULLPTR)
, ready_(false)
{
}
//*************************************************************************
/// Store the reference and mark the state as ready.
//*************************************************************************
void set_value(T& value)
{
ETL_ASSERT(!is_ready(), ETL_ERROR(promise_already_satisfied));
p_value_ = etl::addressof(value);
ready_.store(true, etl::memory_order_release);
}
//*************************************************************************
/// Check whether the result is available.
//*************************************************************************
ETL_NODISCARD
bool is_ready() const ETL_NOEXCEPT
{
return ready_.load(etl::memory_order_acquire);
}
//*************************************************************************
/// Get the stored reference.
/// Asserts etl::future_not_ready if the state is not ready.
//*************************************************************************
ETL_NODISCARD
T& get() const
{
ETL_ASSERT(is_ready(), ETL_ERROR(future_not_ready));
return *p_value_;
}
//*************************************************************************
/// Reset for reuse.
//*************************************************************************
void reset()
{
ready_.store(false, etl::memory_order_release);
p_value_ = ETL_NULLPTR;
}
private:
// Non-copyable.
shared_state(const shared_state&) ETL_DELETE;
shared_state& operator=(const shared_state&) ETL_DELETE;
T* p_value_;
etl::atomic_bool ready_;
};
//***************************************************************************
/// Specialisation for void — no value storage, just a ready flag.
//***************************************************************************
template <>
class shared_state<void>
{
public:
shared_state()
: ready_(false)
{
}
void set_value()
{
ETL_ASSERT(!is_ready(), ETL_ERROR(promise_already_satisfied));
ready_.store(true, etl::memory_order_release);
}
ETL_NODISCARD
bool is_ready() const ETL_NOEXCEPT
{
return ready_.load(etl::memory_order_acquire);
}
void reset()
{
ready_.store(false, etl::memory_order_release);
}
private:
shared_state(const shared_state&) ETL_DELETE;
shared_state& operator=(const shared_state&) ETL_DELETE;
etl::atomic_bool ready_;
};
namespace private_future
{
//*************************************************************************
/// The result type of future<T>::get().
/// const T& for values, R& for T = R&.
/// (C++03 has no reference collapsing, so const T& cannot be used for both.)
//*************************************************************************
template <typename T>
struct future_get_result
{
typedef const T& type;
};
template <typename T>
struct future_get_result<T&>
{
typedef T& type;
};
} // namespace private_future
//***************************************************************************
/// A lightweight future — a non-owning handle to a shared_state<T>.
/// The shared_state lifetime must be managed externally.
/// Has the semantics of std::shared_future: it is copyable, get() does not
/// consume the value and may be called more than once, by every copy.
/// get() returns const T&, or R& for T = R&.
//***************************************************************************
template <typename T>
class future
{
public:
future() ETL_NOEXCEPT
: state_(ETL_NULLPTR)
{
}
explicit future(shared_state<T>& state) ETL_NOEXCEPT
: state_(&state)
{
}
//*************************************************************************
/// Returns true when the result is available.
//*************************************************************************
ETL_NODISCARD
bool is_ready() const ETL_NOEXCEPT
{
return (state_ != ETL_NULLPTR) && state_->is_ready();
}
//*************************************************************************
/// Returns true if this future is associated with a shared state.
//*************************************************************************
ETL_NODISCARD
bool valid() const ETL_NOEXCEPT
{
return state_ != ETL_NULLPTR;
}
//*************************************************************************
/// Get the result.
/// Asserts etl::future_no_state if not valid (no shared state).
/// Asserts etl::future_not_ready if valid but the result is not ready.
//*************************************************************************
ETL_NODISCARD
typename private_future::future_get_result<T>::type get() const
{
ETL_ASSERT(valid(), ETL_ERROR(future_no_state));
ETL_ASSERT(!valid() || state_->is_ready(), ETL_ERROR(future_not_ready));
return state_->get();
}
private:
shared_state<T>* state_;
};
//***************************************************************************
/// Specialisation for void.
//***************************************************************************
template <>
class future<void>
{
public:
future() ETL_NOEXCEPT
: state_(ETL_NULLPTR)
{
}
explicit future(shared_state<void>& state) ETL_NOEXCEPT
: state_(&state)
{
}
ETL_NODISCARD
bool is_ready() const ETL_NOEXCEPT
{
return (state_ != ETL_NULLPTR) && state_->is_ready();
}
ETL_NODISCARD
bool valid() const ETL_NOEXCEPT
{
return state_ != ETL_NULLPTR;
}
//*************************************************************************
/// Asserts etl::future_no_state if not valid (no shared state).
/// Asserts etl::future_not_ready if valid but the result is not ready.
//*************************************************************************
void get() const
{
ETL_ASSERT(valid(), ETL_ERROR(future_no_state));
ETL_ASSERT(!valid() || state_->is_ready(), ETL_ERROR(future_not_ready));
}
private:
shared_state<void>* state_;
};
namespace private_future
{
//*************************************************************************
/// Common base of the promise specialisations.
/// Like std::promise, a promise is move-only (C++11), so there is only
/// ever one producer. Non-copyable in C++03.
//*************************************************************************
template <typename T>
class promise_base
{
public:
//***********************************************************************
/// Returns true if the promise is associated with a shared state.
//***********************************************************************
ETL_NODISCARD
bool valid() const ETL_NOEXCEPT
{
return state_ != ETL_NULLPTR;
}
//***********************************************************************
/// Get the associated future.
/// Asserts etl::future_no_state if the promise is not valid.
//***********************************************************************
ETL_NODISCARD
etl::future<T> get_future()
{
ETL_ASSERT(valid(), ETL_ERROR(future_no_state));
return etl::future<T>(*state_);
}
protected:
promise_base() ETL_NOEXCEPT
: state_(ETL_NULLPTR)
{
}
explicit promise_base(etl::shared_state<T>& state) ETL_NOEXCEPT
: state_(&state)
{
}
#if ETL_USING_CPP11
//***********************************************************************
/// Move constructor. The moved from promise is no longer valid.
//***********************************************************************
promise_base(promise_base&& other) ETL_NOEXCEPT
: state_(other.state_)
{
other.state_ = ETL_NULLPTR;
}
//***********************************************************************
/// Move assignment. The moved from promise is no longer valid.
//***********************************************************************
promise_base& operator=(promise_base&& other) ETL_NOEXCEPT
{
if (this != &other)
{
state_ = other.state_;
other.state_ = ETL_NULLPTR;
}
return *this;
}
#endif
etl::shared_state<T>* state_;
private:
// Non-copyable.
promise_base(const promise_base&) ETL_DELETE;
promise_base& operator=(const promise_base&) ETL_DELETE;
};
} // namespace private_future
//***************************************************************************
/// A lightweight promise — a non-owning handle that can set the value
/// in a shared_state<T>.
//***************************************************************************
template <typename T>
class promise : public private_future::promise_base<T>
{
public:
promise() ETL_NOEXCEPT
: private_future::promise_base<T>()
{
}
explicit promise(etl::shared_state<T>& state) ETL_NOEXCEPT
: private_future::promise_base<T>(state)
{
}
//*************************************************************************
/// Set the value (fulfil the promise).
//*************************************************************************
void set_value(const T& value)
{
ETL_ASSERT(this->valid(), ETL_ERROR(future_no_state));
this->state_->set_value(value);
}
#if ETL_USING_CPP11
void set_value(T&& value)
{
ETL_ASSERT(this->valid(), ETL_ERROR(future_no_state));
this->state_->set_value(etl::move(value));
}
#endif
};
//***************************************************************************
/// Specialisation for references.
//***************************************************************************
template <typename T>
class promise<T&> : public private_future::promise_base<T&>
{
public:
promise() ETL_NOEXCEPT
: private_future::promise_base<T&>()
{
}
explicit promise(etl::shared_state<T&>& state) ETL_NOEXCEPT
: private_future::promise_base<T&>(state)
{
}
void set_value(T& value)
{
ETL_ASSERT(this->valid(), ETL_ERROR(future_no_state));
this->state_->set_value(value);
}
};
//***************************************************************************
/// Specialisation for void.
//***************************************************************************
template <>
class promise<void> : public private_future::promise_base<void>
{
public:
promise() ETL_NOEXCEPT
: private_future::promise_base<void>()
{
}
explicit promise(etl::shared_state<void>& state) ETL_NOEXCEPT
: private_future::promise_base<void>(state)
{
}
void set_value()
{
ETL_ASSERT(this->valid(), ETL_ERROR(future_no_state));
this->state_->set_value();
}
};
} // namespace etl
#endif // ETL_HAS_ATOMIC
#endif // ETL_FUTURE_INCLUDED

View File

@ -196,6 +196,7 @@ add_executable(etl_tests
test_function.cpp
test_functional.cpp
test_function_traits.cpp
test_future.cpp
test_gamma.cpp
test_hash.cpp
test_hfsm.cpp

View File

@ -184,6 +184,7 @@ etl_test_sources = files(
'test_function.cpp',
'test_function_traits.cpp',
'test_functional.cpp',
'test_future.cpp',
'test_gamma.cpp',
'test_hash.cpp',
'test_hfsm.cpp',

View File

@ -234,6 +234,7 @@ target_sources(tests PRIVATE
function.h.t.cpp
functional.h.t.cpp
function_traits.h.t.cpp
future.h.t.cpp
gamma.h.t.cpp
gcd.h.t.cpp
generic_pool.h.t.cpp

View File

@ -0,0 +1,29 @@
/******************************************************************************
The MIT License(MIT)
Embedded Template Library.
https://github.com/ETLCPP/etl
https://www.etlcpp.com
Copyright(c) 2026 BMW AG
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files(the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions :
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
******************************************************************************/
#include <etl/future.h>

628
test/test_future.cpp Normal file
View File

@ -0,0 +1,628 @@
/******************************************************************************
The MIT License(MIT)
Embedded Template Library.
https://github.com/ETLCPP/etl
https://www.etlcpp.com
Copyright(c) 2026 BMW AG
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files(the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions :
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
******************************************************************************/
#include "unit_test_framework.h"
#include "etl/future.h"
#include <type_traits>
#if ETL_HAS_ATOMIC
namespace
{
//---------------------------------------------------------------------------
// A simple struct to test non-primitive types in future/promise.
//---------------------------------------------------------------------------
struct Point
{
int x;
int y;
bool operator==(const Point& other) const
{
return x == other.x && y == other.y;
}
};
//---------------------------------------------------------------------------
// Counts the number of live objects, to check construction/destruction.
//---------------------------------------------------------------------------
struct Counted
{
static int live;
explicit Counted(int value_)
: value(value_)
{
++live;
}
Counted(const Counted& other)
: value(other.value)
{
++live;
}
~Counted()
{
--live;
}
int value;
};
int Counted::live = 0;
//---------------------------------------------------------------------------
// A move-only type that records whether it has been moved from.
//---------------------------------------------------------------------------
struct MoveOnly
{
explicit MoveOnly(int value_)
: value(value_)
, moved_from(false)
{
}
MoveOnly(MoveOnly&& other)
: value(other.value)
, moved_from(false)
{
other.moved_from = true;
}
MoveOnly(const MoveOnly&) = delete;
MoveOnly& operator=(const MoveOnly&) = delete;
int value;
bool moved_from;
};
SUITE(test_future)
{
//*************************************************************************
// shared_state<T> tests
//*************************************************************************
TEST(test_shared_state_int_default_not_ready)
{
etl::shared_state<int> state;
CHECK(!state.is_ready());
}
TEST(test_shared_state_int_set_value_becomes_ready)
{
etl::shared_state<int> state;
state.set_value(42);
CHECK(state.is_ready());
CHECK_EQUAL(42, state.get());
}
TEST(test_shared_state_int_get_const)
{
etl::shared_state<int> state;
state.set_value(77);
const etl::shared_state<int>& cref = state;
CHECK_EQUAL(77, cref.get());
}
TEST(test_shared_state_int_reset)
{
etl::shared_state<int> state;
state.set_value(10);
CHECK(state.is_ready());
state.reset();
CHECK(!state.is_ready());
}
TEST(test_shared_state_int_reset_when_not_ready)
{
etl::shared_state<int> state;
// Should be safe to reset even when not ready.
state.reset();
CHECK(!state.is_ready());
}
TEST(test_shared_state_double)
{
etl::shared_state<double> state;
state.set_value(3.14);
CHECK(state.is_ready());
CHECK_CLOSE(3.14, state.get(), 0.001);
}
TEST(test_shared_state_struct)
{
etl::shared_state<Point> state;
Point p{10, 20};
state.set_value(p);
CHECK(state.is_ready());
CHECK_EQUAL(10, state.get().x);
CHECK_EQUAL(20, state.get().y);
}
//*************************************************************************
// shared_state<void> tests
//*************************************************************************
TEST(test_shared_state_void_default_not_ready)
{
etl::shared_state<void> state;
CHECK(!state.is_ready());
}
TEST(test_shared_state_void_set_value_becomes_ready)
{
etl::shared_state<void> state;
state.set_value();
CHECK(state.is_ready());
}
TEST(test_shared_state_void_reset)
{
etl::shared_state<void> state;
state.set_value();
CHECK(state.is_ready());
state.reset();
CHECK(!state.is_ready());
}
//*************************************************************************
// future<T> tests
//*************************************************************************
TEST(test_future_int_default_invalid)
{
etl::future<int> f;
CHECK(!f.valid());
CHECK(!f.is_ready());
}
TEST(test_future_int_from_state_valid_not_ready)
{
etl::shared_state<int> state;
etl::future<int> f(state);
CHECK(f.valid());
CHECK(!f.is_ready());
}
TEST(test_future_int_becomes_ready_after_set)
{
etl::shared_state<int> state;
etl::future<int> f(state);
state.set_value(55);
CHECK(f.is_ready());
CHECK_EQUAL(55, f.get());
}
TEST(test_future_int_const_get)
{
etl::shared_state<int> state;
state.set_value(88);
const etl::future<int> f(state);
CHECK(f.is_ready());
CHECK_EQUAL(88, f.get());
}
TEST(test_future_double)
{
etl::shared_state<double> state;
etl::future<double> f(state);
state.set_value(2.718);
CHECK(f.is_ready());
CHECK_CLOSE(2.718, f.get(), 0.001);
}
TEST(test_future_struct)
{
etl::shared_state<Point> state;
etl::future<Point> f(state);
state.set_value(Point{3, 4});
CHECK(f.is_ready());
CHECK_EQUAL(3, f.get().x);
CHECK_EQUAL(4, f.get().y);
}
//*************************************************************************
// future<void> tests
//*************************************************************************
TEST(test_future_void_default_invalid)
{
etl::future<void> f;
CHECK(!f.valid());
CHECK(!f.is_ready());
}
TEST(test_future_void_from_state)
{
etl::shared_state<void> state;
etl::future<void> f(state);
CHECK(f.valid());
CHECK(!f.is_ready());
state.set_value();
CHECK(f.is_ready());
}
//*************************************************************************
// promise<T> tests
//*************************************************************************
TEST(test_promise_int_default_invalid)
{
etl::promise<int> p;
CHECK(!p.valid());
}
TEST(test_promise_int_set_value_fulfils_future)
{
etl::shared_state<int> state;
etl::promise<int> p(state);
etl::future<int> f = p.get_future();
CHECK(f.valid());
CHECK(!f.is_ready());
p.set_value(123);
CHECK(f.is_ready());
CHECK_EQUAL(123, f.get());
}
TEST(test_promise_double)
{
etl::shared_state<double> state;
etl::promise<double> p(state);
etl::future<double> f = p.get_future();
p.set_value(1.41421);
CHECK(f.is_ready());
CHECK_CLOSE(1.41421, f.get(), 0.00001);
}
TEST(test_promise_struct)
{
etl::shared_state<Point> state;
etl::promise<Point> p(state);
etl::future<Point> f = p.get_future();
p.set_value(Point{-5, 7});
CHECK(f.is_ready());
CHECK_EQUAL(-5, f.get().x);
CHECK_EQUAL(7, f.get().y);
}
//*************************************************************************
// promise<void> tests
//*************************************************************************
TEST(test_promise_void_default_invalid)
{
etl::promise<void> p;
CHECK(!p.valid());
}
TEST(test_promise_void_set_value_fulfils_future)
{
etl::shared_state<void> state;
etl::promise<void> p(state);
etl::future<void> f = p.get_future();
CHECK(f.valid());
CHECK(!f.is_ready());
p.set_value();
CHECK(f.is_ready());
}
//*************************************************************************
// Integration / edge-case tests
//*************************************************************************
TEST(test_multiple_futures_same_state)
{
// Two futures can observe the same shared_state.
etl::shared_state<int> state;
etl::future<int> f1(state);
etl::future<int> f2(state);
CHECK(!f1.is_ready());
CHECK(!f2.is_ready());
state.set_value(999);
CHECK(f1.is_ready());
CHECK(f2.is_ready());
CHECK_EQUAL(999, f1.get());
CHECK_EQUAL(999, f2.get());
}
TEST(test_shared_state_reuse_after_reset)
{
etl::shared_state<int> state;
etl::promise<int> p(state);
etl::future<int> f = p.get_future();
p.set_value(1);
CHECK_EQUAL(1, f.get());
// Reset and reuse.
state.reset();
CHECK(!f.is_ready());
// Set a different value.
state.set_value(2);
CHECK(f.is_ready());
CHECK_EQUAL(2, f.get());
}
TEST(test_move_semantics_set_value)
{
etl::shared_state<MoveOnly> state;
etl::promise<MoveOnly> p(state);
etl::future<MoveOnly> f = p.get_future();
MoveOnly value(50);
p.set_value(etl::move(value));
CHECK(value.moved_from);
CHECK(f.is_ready());
CHECK_EQUAL(50, f.get().value);
CHECK(!f.get().moved_from);
}
//*************************************************************************
// Lifetime of the stored value
//*************************************************************************
TEST(test_shared_state_destroys_value)
{
Counted::live = 0;
{
etl::shared_state<Counted> state;
CHECK_EQUAL(0, Counted::live);
state.set_value(Counted(1));
CHECK_EQUAL(1, Counted::live);
}
CHECK_EQUAL(0, Counted::live);
}
TEST(test_shared_state_not_ready_destroys_nothing)
{
Counted::live = 0;
{
etl::shared_state<Counted> state;
}
CHECK_EQUAL(0, Counted::live);
}
TEST(test_shared_state_reset_destroys_value_and_reuse)
{
Counted::live = 0;
{
etl::shared_state<Counted> state;
state.set_value(Counted(1));
CHECK_EQUAL(1, Counted::live);
state.reset();
CHECK_EQUAL(0, Counted::live);
CHECK(!state.is_ready());
state.set_value(Counted(2));
CHECK_EQUAL(1, Counted::live);
CHECK_EQUAL(2, state.get().value);
}
CHECK_EQUAL(0, Counted::live);
}
//*************************************************************************
// Reference specialisations
//*************************************************************************
TEST(test_reference)
{
int value = 1;
etl::shared_state<int&> state;
etl::promise<int&> p(state);
etl::future<int&> f = p.get_future();
CHECK(!f.is_ready());
p.set_value(value);
CHECK(f.is_ready());
CHECK(&f.get() == &value);
f.get() = 2;
CHECK_EQUAL(2, value);
state.reset();
CHECK(!f.is_ready());
int other = 3;
p.set_value(other);
CHECK(&f.get() == &other);
}
//*************************************************************************
// Types
//*************************************************************************
TEST(test_future_get_return_types)
{
CHECK((std::is_same<const int&, decltype(etl::declval<etl::future<int>&>().get())>::value));
CHECK((std::is_same<const int&, decltype(etl::declval<const etl::future<int>&>().get())>::value));
CHECK((std::is_same<int&, decltype(etl::declval<etl::future<int&>&>().get())>::value));
CHECK((std::is_same<void, decltype(etl::declval<etl::future<void>&>().get())>::value));
}
TEST(test_future_is_copyable)
{
CHECK(std::is_copy_constructible<etl::future<int>>::value);
CHECK(std::is_copy_assignable<etl::future<int>>::value);
etl::shared_state<int> state;
etl::future<int> f1(state);
etl::future<int> f2(f1);
state.set_value(5);
CHECK_EQUAL(5, f1.get());
CHECK_EQUAL(5, f2.get());
CHECK_EQUAL(5, f2.get()); // get() does not consume the value.
}
TEST(test_promise_is_move_only)
{
CHECK(!std::is_copy_constructible<etl::promise<int>>::value);
CHECK(!std::is_copy_assignable<etl::promise<int>>::value);
CHECK(std::is_nothrow_move_constructible<etl::promise<int>>::value);
CHECK(std::is_nothrow_move_assignable<etl::promise<int>>::value);
CHECK(!std::is_copy_constructible<etl::promise<int&>>::value);
CHECK(std::is_nothrow_move_constructible<etl::promise<int&>>::value);
CHECK(!std::is_copy_constructible<etl::promise<void>>::value);
CHECK(std::is_nothrow_move_constructible<etl::promise<void>>::value);
}
TEST(test_promise_move_construct)
{
etl::shared_state<int> state;
etl::promise<int> p1(state);
etl::promise<int> p2(etl::move(p1));
CHECK(!p1.valid());
CHECK(p2.valid());
etl::future<int> f = p2.get_future();
p2.set_value(7);
CHECK_EQUAL(7, f.get());
}
TEST(test_promise_move_assign)
{
etl::shared_state<void> state;
etl::promise<void> p1(state);
etl::promise<void> p2;
CHECK(!p2.valid());
p2 = etl::move(p1);
CHECK(!p1.valid());
CHECK(p2.valid());
etl::future<void> f = p2.get_future();
p2.set_value();
CHECK(f.is_ready());
}
//*************************************************************************
// Errors
//*************************************************************************
TEST(test_shared_state_get_not_ready)
{
etl::shared_state<int> state;
const etl::shared_state<int>& cstate = state;
etl::shared_state<int&> rstate;
CHECK_THROW((void)state.get(), etl::future_not_ready);
CHECK_THROW((void)cstate.get(), etl::future_not_ready);
CHECK_THROW((void)rstate.get(), etl::future_not_ready);
}
TEST(test_shared_state_set_value_twice)
{
etl::shared_state<int> state;
state.set_value(1);
CHECK_THROW(state.set_value(2), etl::promise_already_satisfied);
CHECK_EQUAL(1, state.get());
int a = 1;
int b = 2;
etl::shared_state<int&> rstate;
rstate.set_value(a);
CHECK_THROW(rstate.set_value(b), etl::promise_already_satisfied);
CHECK(&rstate.get() == &a);
etl::shared_state<void> vstate;
vstate.set_value();
CHECK_THROW(vstate.set_value(), etl::promise_already_satisfied);
}
TEST(test_promise_set_value_twice)
{
Counted::live = 0;
{
etl::shared_state<Counted> state;
etl::promise<Counted> p(state);
p.set_value(Counted(1));
CHECK_THROW(p.set_value(Counted(2)), etl::promise_already_satisfied);
CHECK_EQUAL(1, state.get().value);
CHECK_EQUAL(1, Counted::live);
}
CHECK_EQUAL(0, Counted::live);
}
TEST(test_future_get_not_ready)
{
etl::shared_state<int> state;
etl::future<int> f(state);
etl::shared_state<void> vstate;
etl::future<void> vf(vstate);
CHECK_THROW((void)f.get(), etl::future_not_ready);
CHECK_THROW(vf.get(), etl::future_not_ready);
}
TEST(test_future_get_no_state)
{
etl::future<int> f;
etl::future<int&> rf;
etl::future<void> vf;
CHECK_THROW((void)f.get(), etl::future_no_state);
CHECK_THROW((void)rf.get(), etl::future_no_state);
CHECK_THROW(vf.get(), etl::future_no_state);
}
TEST(test_promise_no_state)
{
int value = 1;
etl::promise<int> p;
etl::promise<int&> rp;
etl::promise<void> vp;
CHECK_THROW((void)p.get_future(), etl::future_no_state);
CHECK_THROW(p.set_value(1), etl::future_no_state);
CHECK_THROW((void)rp.get_future(), etl::future_no_state);
CHECK_THROW(rp.set_value(value), etl::future_no_state);
CHECK_THROW((void)vp.get_future(), etl::future_no_state);
CHECK_THROW(vp.set_value(), etl::future_no_state);
}
TEST(test_promise_no_state_after_move)
{
etl::shared_state<int> state;
etl::promise<int> p1(state);
etl::promise<int> p2(etl::move(p1));
CHECK_THROW(p1.set_value(1), etl::future_no_state);
CHECK(!state.is_ready());
}
TEST(test_exceptions_derive_from_future_exception)
{
etl::future<int> f;
CHECK_THROW((void)f.get(), etl::future_exception);
etl::promise<int> p;
CHECK_THROW(p.set_value(1), etl::future_exception);
etl::shared_state<int> state;
state.set_value(1);
CHECK_THROW(state.set_value(2), etl::future_exception);
}
}
} // namespace
#endif // ETL_HAS_ATOMIC

View File

@ -10395,6 +10395,7 @@
<ClCompile Include="..\test_expected.cpp" />
<ClCompile Include="..\test_format.cpp" />
<ClCompile Include="..\test_function_traits.cpp" />
<ClCompile Include="..\test_future.cpp" />
<ClCompile Include="..\test_hfsm_recurse_to_inner_state_on_start.cpp" />
<ClCompile Include="..\test_hfsm_transition_on_enter.cpp" />
<ClCompile Include="..\test_index_of_type.cpp" />

View File

@ -3587,6 +3587,9 @@
<ClCompile Include="..\test_function_traits.cpp">
<Filter>Tests\Types</Filter>
</ClCompile>
<ClCompile Include="..\test_future.cpp">
<Filter>Tests\Misc</Filter>
</ClCompile>
<ClCompile Include="..\test_singleton_base.cpp">
<Filter>Tests\Patterns</Filter>
</ClCompile>