Skip to content
Open
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
2 changes: 2 additions & 0 deletions .github/actions/spelling/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
Bies
binaryfile
BINDIR
Bitfield

Check warning on line 48 in .github/actions/spelling/expect.txt

View workflow job for this annotation

GitHub Actions / Check Spelling

`Bitfield` is ignored by check-spelling because another more general variant is also in expect (ignored-expect-variant)
bitfield
bitmaps
bitshifts
Expand Down Expand Up @@ -74,8 +74,8 @@
callgraph
Campuzano
carg
CBE

Check warning on line 77 in .github/actions/spelling/expect.txt

View workflow job for this annotation

GitHub Actions / Check Spelling

`CBE` is ignored by check-spelling because another more general variant is also in expect (ignored-expect-variant)
CBEs

Check warning on line 78 in .github/actions/spelling/expect.txt

View workflow job for this annotation

GitHub Actions / Check Spelling

`CBEs` is ignored by check-spelling because another more general variant is also in expect (ignored-expect-variant)
CBF
CBLOCK
CCACHE
Expand All @@ -87,6 +87,7 @@
cdh
CDHCORE
CDHCORESUBTOPOLOGY
celskeggs
cerrno
CFDP
cff
Expand Down Expand Up @@ -120,7 +121,7 @@
comlogger
COMLOGGERTEE
commandability
Commandability

Check warning on line 124 in .github/actions/spelling/expect.txt

View workflow job for this annotation

GitHub Actions / Check Spelling

`Commandability` is ignored by check-spelling because another more general variant is also in expect (ignored-expect-variant)
COMMANDDISPATCHERIMPL
COMMANDDISPATCHERIMPLCFG
COMPACKET
Expand All @@ -130,10 +131,11 @@
COMSPLITTER
COMSTUB
constexpr
COUNTINGSEMAPHORE

Check warning on line 134 in .github/actions/spelling/expect.txt

View workflow job for this annotation

GitHub Actions / Check Spelling

`COUNTINGSEMAPHORE` is ignored by check-spelling because another more general variant is also in expect (ignored-expect-variant)
countingsemaphore
cookiecutter
cooldown
copyable
coravy
coreutils
Coverity
Expand Down Expand Up @@ -230,7 +232,7 @@
endmacro
endraw
enduml
EPP

Check warning on line 235 in .github/actions/spelling/expect.txt

View workflow job for this annotation

GitHub Actions / Check Spelling

`EPP` is ignored by check-spelling because another more general variant is also in expect (ignored-expect-variant)
epp
ERRORCHECK
errornum
Expand Down Expand Up @@ -555,8 +557,8 @@
Peet
penv
PERLMOD
PFR

Check warning on line 560 in .github/actions/spelling/expect.txt

View workflow job for this annotation

GitHub Actions / Check Spelling

`PFR` is ignored by check-spelling because another more general variant is also in expect (ignored-expect-variant)
PFRs

Check warning on line 561 in .github/actions/spelling/expect.txt

View workflow job for this annotation

GitHub Actions / Check Spelling

`PFRs` is ignored by check-spelling because another more general variant is also in expect (ignored-expect-variant)
PHASERMEMBEROUT
PINGENTRIES
PINGIN
Expand Down Expand Up @@ -787,7 +789,7 @@
trinomials
trywait
tts
Tumbar

Check warning on line 792 in .github/actions/spelling/expect.txt

View workflow job for this annotation

GitHub Actions / Check Spelling

`Tumbar` is ignored by check-spelling because another more general variant is also in expect (ignored-expect-variant)
tumbar
typedef
typedef'ed
Expand Down
8 changes: 8 additions & 0 deletions Fw/Types/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -89,5 +89,13 @@ register_fprime_ut(
Fw_StringScan_sscanf
)

register_fprime_ut(
Fw_Types_Optional_ut_exe
SOURCES
"${CMAKE_CURRENT_LIST_DIR}/test/ut/OptionalTest.cpp"
DEPENDS
Fw_Types
)

# Non-test directory
add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/GTest")
172 changes: 172 additions & 0 deletions Fw/Types/Optional.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
// ======================================================================
// \title Optional.hpp
// \author celskeggs
// \brief hpp file for Optional type
//
// \description
// A lightweight optional type for C++14 environments where
// std::optional (C++17) is not available.
//
// \copyright
// Copyright (C) 2025-2026 California Institute of Technology.
// ALL RIGHTS RESERVED. United States Government Sponsorship
// acknowledged.
//
// ======================================================================

#ifndef Fw_Optional_HPP
#define Fw_Optional_HPP

#include <Fw/Types/Assert.hpp>
#include <type_traits>

namespace Fw {

//! \brief Sentinel type representing the absence of a value in an Optional
struct Absent {
constexpr explicit Absent() {}
};

//! \brief Global constant representing an absent/empty optional value
constexpr Absent ABSENT = Absent();

//! \class Optional
//! \brief A type-safe container for an optional value
//!
//! Provides a C++14-compatible alternative to std::optional.
//! Only supports trivially copyable types (no non-trivial constructors,
//! destructors, or copy/move operators).
//!
//! \tparam T The type of the contained value. Must be trivially copyable.
//!
template <typename T>
class Optional {
static_assert(std::is_trivially_copyable<T>::value, "Fw::Optional only supports trivially copyable types");

union {
char m_dummy;
T m_val;
};
bool m_engaged;

public:
// ----------------------------------------------------------------------
// Construction
// ----------------------------------------------------------------------

//! Construct an empty Optional
constexpr Optional() : m_dummy(0), m_engaged(false) {}

//! Construct an empty Optional from ABSENT sentinel
constexpr Optional(const Absent& a) : Optional() { (void)a; }

Check warning

Code scanning / CppCheck

Single-parameter constructors should be marked explicit. Warning

Single-parameter constructors should be marked explicit.

//! Construct an Optional containing a value
constexpr Optional(const T& t) : m_val(t), m_engaged(true) {}

Check warning

Code scanning / CppCheck

Single-parameter constructors should be marked explicit. Warning

Single-parameter constructors should be marked explicit.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed

//! Copy constructor
Optional(const Optional& other) : Optional() {
if (other.has_value()) {
*this = other.value();
}
}

//! Destructor (trivial — T must be trivially copyable)
~Optional() = default;

// ----------------------------------------------------------------------
// Observers
// ----------------------------------------------------------------------

//! Check whether the Optional contains a value
//! \return true if a value is present
bool has_value() const { return m_engaged; }

//! Access the contained value
//! \pre has_value() must be true
//! \return Mutable reference to the contained value
T& value() {
FW_ASSERT(m_engaged);
return m_val;
}

//! Access the contained value (const)
//! \pre has_value() must be true
//! \return Const reference to the contained value
const T& value() const {
FW_ASSERT(m_engaged);
return m_val;
}

//! Access the value or return a default
//! \return The contained value if present, otherwise the provided default
T value_or(const T& default_value) const { return m_engaged ? m_val : default_value; }

// ----------------------------------------------------------------------
// Modifiers
// ----------------------------------------------------------------------

//! Reset the Optional to an empty state
void reset() { m_engaged = false; }

// ----------------------------------------------------------------------
// Assignment operators
// ----------------------------------------------------------------------

//! Assign a value into the Optional
Optional& operator=(const T& t) {
m_engaged = true;
m_val = t;
return *this;
}
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.

//! Reset the Optional via ABSENT assignment
Optional& operator=(const Absent& a) {
(void)a;
reset();
return *this;
}

//! Copy assignment operator
Optional& operator=(const Optional& other) {
if (other.has_value()) {
*this = other.value();
} else {
reset();
}
return *this;
}

// ----------------------------------------------------------------------
// Comparison operators
// ----------------------------------------------------------------------

//! Compare with ABSENT sentinel
bool operator==(const Absent& a) const {
(void)a;
return !m_engaged;
}

//! Compare with ABSENT sentinel (inequality)
bool operator!=(const Absent& a) const {
(void)a;
return m_engaged;
}

//! Equality comparison with another Optional
bool operator==(const Optional& other) const {
if (m_engaged != other.m_engaged) {
return false;
}
if (!m_engaged) {
return true;
}
return m_val == other.m_val;
}

//! Inequality comparison with another Optional
bool operator!=(const Optional& other) const { return !(*this == other); }
};

} // namespace Fw

#endif
168 changes: 168 additions & 0 deletions Fw/Types/test/ut/OptionalTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
#include <gtest/gtest.h>
#include <Fw/Types/Optional.hpp>

// Test with a simple integer type
TEST(OptionalTest, DefaultConstructIsEmpty) {
Fw::Optional<I32> opt;
ASSERT_FALSE(opt.has_value());
ASSERT_EQ(opt, Fw::ABSENT);
}

TEST(OptionalTest, AbsentConstructIsEmpty) {
Fw::Optional<I32> opt(Fw::ABSENT);
ASSERT_FALSE(opt.has_value());
}

TEST(OptionalTest, ValueConstructHasValue) {
Fw::Optional<I32> opt(42);
ASSERT_TRUE(opt.has_value());
ASSERT_EQ(opt.value(), 42);
}

TEST(OptionalTest, CopyConstructEmpty) {
Fw::Optional<I32> opt1;
Fw::Optional<I32> opt2(opt1);
ASSERT_FALSE(opt2.has_value());
}

TEST(OptionalTest, CopyConstructWithValue) {
Fw::Optional<I32> opt1(99);
Fw::Optional<I32> opt2(opt1);
ASSERT_TRUE(opt2.has_value());
ASSERT_EQ(opt2.value(), 99);
}

TEST(OptionalTest, AssignValue) {
Fw::Optional<I32> opt;
opt = 7;
ASSERT_TRUE(opt.has_value());
ASSERT_EQ(opt.value(), 7);
}

TEST(OptionalTest, AssignAbsent) {
Fw::Optional<I32> opt(10);
opt = Fw::ABSENT;
ASSERT_FALSE(opt.has_value());
}

TEST(OptionalTest, Reset) {
Fw::Optional<I32> opt(5);
ASSERT_TRUE(opt.has_value());
opt.reset();
ASSERT_FALSE(opt.has_value());
}

TEST(OptionalTest, ValueOrWithValue) {
Fw::Optional<I32> opt(42);
ASSERT_EQ(opt.value_or(0), 42);
}

TEST(OptionalTest, ValueOrWithoutValue) {
Fw::Optional<I32> opt;
ASSERT_EQ(opt.value_or(99), 99);
}

TEST(OptionalTest, MutableValueAccess) {
Fw::Optional<I32> opt(10);
opt.value() = 20;
ASSERT_EQ(opt.value(), 20);
}

TEST(OptionalTest, CopyAssignmentWithValue) {
Fw::Optional<I32> opt1(42);
Fw::Optional<I32> opt2;
opt2 = opt1;
ASSERT_TRUE(opt2.has_value());
ASSERT_EQ(opt2.value(), 42);
}

TEST(OptionalTest, CopyAssignmentWithEmpty) {
Fw::Optional<I32> opt1;
Fw::Optional<I32> opt2(42);
opt2 = opt1;
ASSERT_FALSE(opt2.has_value());
}

TEST(OptionalTest, EqualityBothEmpty) {
Fw::Optional<I32> opt1;
Fw::Optional<I32> opt2;
ASSERT_EQ(opt1, opt2);
}

TEST(OptionalTest, EqualitySameValue) {
Fw::Optional<I32> opt1(42);
Fw::Optional<I32> opt2(42);
ASSERT_EQ(opt1, opt2);
}

TEST(OptionalTest, InequalityDifferentValues) {
Fw::Optional<I32> opt1(1);
Fw::Optional<I32> opt2(2);
ASSERT_NE(opt1, opt2);
}

TEST(OptionalTest, InequalityOneEmpty) {
Fw::Optional<I32> opt1(1);
Fw::Optional<I32> opt2;
ASSERT_NE(opt1, opt2);
}

TEST(OptionalTest, AbsentComparisonOperators) {
Fw::Optional<I32> empty;
Fw::Optional<I32> full(42);
ASSERT_TRUE(empty == Fw::ABSENT);
ASSERT_FALSE(empty != Fw::ABSENT);
ASSERT_FALSE(full == Fw::ABSENT);
ASSERT_TRUE(full != Fw::ABSENT);
}

TEST(OptionalTest, ReassignValue) {
Fw::Optional<I32> opt(1);
opt = 2;
ASSERT_EQ(opt.value(), 2);
opt = 3;
ASSERT_EQ(opt.value(), 3);
}

// Test with a struct type
struct TestStruct {
I32 x;
I32 y;
bool operator==(const TestStruct& other) const { return x == other.x && y == other.y; }
};

TEST(OptionalTest, StructValue) {
TestStruct s{10, 20};
Fw::Optional<TestStruct> opt(s);
ASSERT_TRUE(opt.has_value());
ASSERT_EQ(opt.value().x, 10);
ASSERT_EQ(opt.value().y, 20);
}

TEST(OptionalTest, StructAssign) {
Fw::Optional<TestStruct> opt;
TestStruct s{5, 6};
opt = s;
ASSERT_TRUE(opt.has_value());
ASSERT_EQ(opt.value().x, 5);
ASSERT_EQ(opt.value().y, 6);
}

// Test with a floating point type
TEST(OptionalTest, FloatingPointValue) {
Fw::Optional<F64> opt(3.14);
ASSERT_TRUE(opt.has_value());
ASSERT_DOUBLE_EQ(opt.value(), 3.14);
}

// Test constexpr construction
TEST(OptionalTest, ConstexprEmpty) {
constexpr Fw::Optional<I32> opt;
ASSERT_FALSE(opt.has_value());
}

TEST(OptionalTest, ConstexprWithValue) {
constexpr Fw::Optional<I32> opt(42);
ASSERT_TRUE(opt.has_value());
ASSERT_EQ(opt.value(), 42);
}
Loading