blob: 67737d6920c203977fbaaf079a3bf190c199fd64 [file]
// Copyright 2025 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#include "pw_containers/optional_tuple.h"
#include <string_view>
#include <tuple>
#include <utility>
#include "pw_compilation_testing/negative_compilation.h"
#include "pw_containers/internal/test_helpers.h"
#include "pw_polyfill/language_feature_macros.h"
#include "pw_polyfill/standard.h"
#include "pw_unit_test/framework.h"
namespace {
using ::pw::containers::test::Counter;
using ::pw::containers::test::MoveOnly;
using ::pw::containers::test::TrivialMoveOnly;
static_assert(
sizeof(pw::OptionalTuple<uint32_t, uint8_t, uint8_t, uint8_t>) ==
sizeof(std::tuple<uint32_t, uint8_t, uint8_t, uint8_t, uint8_t>),
"OptionalTuple should pack like a tuple with one extra field");
TEST(OptionalTuple, Empty) {
pw::OptionalTuple<> tuple;
EXPECT_TRUE(tuple.empty());
EXPECT_EQ(tuple.count(), 0u);
EXPECT_EQ(tuple.size(), 0u);
}
TEST(OptionalTuple, Empty_Copy) {
pw::OptionalTuple<> tuple;
pw::OptionalTuple<> copy_constructed(tuple);
EXPECT_TRUE(copy_constructed.empty());
EXPECT_EQ(copy_constructed.count(), 0u);
EXPECT_EQ(copy_constructed.size(), 0u);
}
TEST(OptionalTuple, Empty_CopyAssign) {
pw::OptionalTuple<> tuple;
pw::OptionalTuple<> copy_assigned = tuple;
EXPECT_TRUE(copy_assigned.empty());
EXPECT_EQ(copy_assigned.count(), 0u);
EXPECT_EQ(copy_assigned.size(), 0u);
}
TEST(OptionalTuple, Empty_Move) {
pw::OptionalTuple<> tuple;
pw::OptionalTuple<> move_constructed(std::move(tuple));
EXPECT_TRUE(move_constructed.empty());
EXPECT_EQ(move_constructed.count(), 0u);
EXPECT_EQ(move_constructed.size(), 0u);
}
TEST(OptionalTuple, Empty_MoveAssign) {
pw::OptionalTuple<> tuple;
pw::OptionalTuple<> move_assigned;
move_assigned = pw::OptionalTuple<>();
EXPECT_TRUE(move_assigned.empty());
EXPECT_EQ(move_assigned.count(), 0u);
EXPECT_EQ(move_assigned.size(), 0u);
}
TEST(OptionalTuple, DefaultConstruction) {
pw::OptionalTuple<int, bool> tuple;
EXPECT_TRUE(tuple.empty());
EXPECT_FALSE(tuple.has_value<0>());
EXPECT_FALSE(tuple.has_value<int>());
EXPECT_FALSE(tuple.has_value<1>());
EXPECT_FALSE(tuple.has_value<bool>());
#if PW_NC_TEST(OutOfRangeAccess)
PW_NC_EXPECT("The specified bit index is out of range");
std::ignore = tuple.has_value<2>();
#endif // PW_NC_TEST
}
TEST(OptionalTuple, SameTypes) {
pw::OptionalTuple<int, int, int> tuple(pw::kTupleNull, 1, 2);
EXPECT_FALSE(tuple.has_value<0>());
#if PW_NC_TEST(CannotAccessMultiple)
PW_NC_EXPECT("type must appear exactly once in the OptionalTuple");
std::ignore = tuple.has_value<int>();
#endif // PW_NC_TEST
}
TEST(OptionalTuple, Constinit_Default) {
PW_CONSTINIT static pw::OptionalTuple<int, TrivialMoveOnly, bool> tuple;
EXPECT_FALSE(tuple.has_value<0>());
EXPECT_FALSE(tuple.has_value<1>());
EXPECT_FALSE(tuple.has_value<2>());
EXPECT_EQ(tuple.size(), 3u);
EXPECT_EQ(tuple.count(), 0u);
tuple.emplace<1>(123);
EXPECT_EQ(tuple.value<1>().value, 123);
EXPECT_EQ(tuple.count(), 1u);
tuple.reset<1>();
EXPECT_FALSE(tuple.has_value<1>());
}
TEST(OptionalTuple, Constinit_Initialized) {
PW_CONSTINIT static pw::OptionalTuple<const char*, TrivialMoveOnly, int>
tuple("?", TrivialMoveOnly(42), pw::kTupleNull);
EXPECT_EQ(tuple.count(), 2u);
EXPECT_TRUE(tuple.has_value<0>());
EXPECT_TRUE(tuple.has_value<1>());
EXPECT_FALSE(tuple.has_value<2>());
EXPECT_STREQ(tuple.value<0>(), "?");
EXPECT_EQ(tuple.value<1>().value, 42);
}
TEST(OptionalTuple, ValueConstruction_AllSet) {
pw::OptionalTuple<MoveOnly, bool, const char*> tuple(
MoveOnly(100), false, "hello");
EXPECT_FALSE(tuple.empty());
EXPECT_EQ(tuple.value<0>().value, 100);
EXPECT_FALSE(tuple.value<1>());
EXPECT_STREQ(tuple.value<2>(), "hello");
}
TEST(OptionalTuple, ValueConstruction_AllSet_Copy) {
pw::OptionalTuple<int, bool, const char*> tuple(100, false, "hello");
EXPECT_FALSE(tuple.empty());
EXPECT_EQ(tuple.count(), 3u);
EXPECT_EQ(tuple.size(), 3u);
EXPECT_EQ(tuple.value<0>(), 100);
EXPECT_FALSE(tuple.value<1>());
EXPECT_STREQ(tuple.value<2>(), "hello");
}
TEST(OptionalTuple, ValueConstruction_MixedSet) {
pw::OptionalTuple<bool, int, const char*> tuple(pw::kTupleNull, 100, "hello");
EXPECT_FALSE(tuple.empty());
EXPECT_EQ(tuple.count(), 2u);
EXPECT_EQ(tuple.size(), 3u);
EXPECT_FALSE(tuple.has_value<0>());
ASSERT_TRUE(tuple.has_value<1>());
EXPECT_EQ(tuple.value<1>(), 100);
ASSERT_TRUE(tuple.has_value<2>());
EXPECT_STREQ(tuple.value<2>(), "hello");
}
TEST(OptionalTuple, Reset) {
pw::OptionalTuple<int, bool, const char*> tuple(1, true, "foo");
EXPECT_EQ(tuple.count(), 3u);
tuple.reset<1>();
EXPECT_TRUE(tuple.has_value<0>());
EXPECT_FALSE(tuple.has_value<1>());
EXPECT_TRUE(tuple.has_value<2>());
EXPECT_EQ(tuple.value<0>(), 1);
EXPECT_STREQ(tuple.value<2>(), "foo");
tuple.reset<0>();
EXPECT_FALSE(tuple.has_value<0>());
EXPECT_FALSE(tuple.has_value<1>());
EXPECT_TRUE(tuple.has_value<2>());
tuple.reset<2>();
EXPECT_FALSE(tuple.has_value<0>());
EXPECT_FALSE(tuple.has_value<1>());
EXPECT_FALSE(tuple.has_value<2>());
EXPECT_TRUE(tuple.empty());
}
TEST(OptionalTuple, Emplace_IntoEmpty) {
pw::OptionalTuple<int, bool, std::string_view> tuple;
EXPECT_TRUE(tuple.empty());
#if PW_NC_TEST(CannotEmplaceWithTupleNull)
PW_NC_EXPECT("OptionalTupleNullPlaceholder");
tuple.emplace<0>(pw::kTupleNull);
#endif // PW_NC_TEST
EXPECT_EQ(tuple.emplace<0>(31), 31);
EXPECT_TRUE(tuple.has_value<0>());
EXPECT_EQ(tuple.value<0>(), 31);
EXPECT_FALSE(tuple.has_value<1>());
EXPECT_FALSE(tuple.has_value<2>());
EXPECT_TRUE(tuple.emplace<1>(true));
EXPECT_TRUE(tuple.has_value<0>());
EXPECT_TRUE(tuple.has_value<1>());
EXPECT_TRUE(tuple.value<1>());
EXPECT_FALSE(tuple.has_value<2>());
}
TEST(OptionalTuple, Emplace_OverExisting) {
pw::OptionalTuple<int, bool, std::string_view> tuple;
EXPECT_EQ(tuple.emplace<0>(42), 42);
EXPECT_TRUE(tuple.has_value<0>());
EXPECT_EQ(tuple.value<0>(), 42);
}
TEST(OptionalTuple, Emplace_MultipleArgs) {
pw::OptionalTuple<int, bool, std::string_view> tuple;
tuple.emplace<std::string_view>("hello", 1u);
ASSERT_TRUE(tuple.has_value<2>());
EXPECT_EQ(tuple.value<std::string_view>(), "h");
}
TEST(OptionalTuple, AccessByType) {
pw::OptionalTuple<int, bool, std::string_view> tuple;
EXPECT_FALSE(tuple.has_value<int>());
tuple.emplace<int>(123);
EXPECT_TRUE(tuple.has_value<int>());
EXPECT_EQ(tuple.value<int>(), 123);
EXPECT_FALSE(tuple.has_value<bool>());
tuple.emplace<bool>(true);
EXPECT_TRUE(tuple.has_value<bool>());
EXPECT_TRUE(tuple.value<bool>());
EXPECT_FALSE(tuple.has_value<std::string_view>());
tuple.emplace<std::string_view>("test");
EXPECT_TRUE(tuple.has_value<std::string_view>());
EXPECT_EQ(tuple.value<std::string_view>(), "test");
tuple.reset<int>();
EXPECT_FALSE(tuple.has_value<int>());
EXPECT_TRUE(tuple.has_value<bool>());
EXPECT_TRUE(tuple.has_value<std::string_view>());
}
TEST(OptionalTuple, MoveValue) {
pw::OptionalTuple<MoveOnly, int> tuple(MoveOnly(42), 99);
EXPECT_TRUE(tuple.has_value<0>());
MoveOnly moved = std::move(tuple.value<0>());
EXPECT_EQ(moved.value, 42);
EXPECT_TRUE(tuple.has_value<0>()) << "moved value is still present";
// NOLINTNEXTLINE(bugprone-use-after-move)
EXPECT_EQ(tuple.value<0>().value, MoveOnly::kMoved);
}
TEST(OptionalTuple, RvalueValue) {
pw::OptionalTuple<MoveOnly, int> tuple(MoveOnly(42), 99);
EXPECT_TRUE(tuple.has_value<0>());
MoveOnly moved = std::move(tuple).value<0>();
EXPECT_EQ(moved.value, 42);
// NOLINTNEXTLINE(bugprone-use-after-move)
EXPECT_EQ(tuple.value<0>().value, MoveOnly::kMoved);
// NOLINTNEXTLINE(bugprone-use-after-move)
EXPECT_TRUE(tuple.has_value<0>()) << "moved value is still present";
}
TEST(OptionalTuple, RvalueValueByType) {
pw::OptionalTuple<int, MoveOnly> tuple(99, MoveOnly(42));
EXPECT_TRUE(tuple.has_value<MoveOnly>());
MoveOnly moved = std::move(tuple).value<MoveOnly>();
EXPECT_EQ(moved.value, 42);
// NOLINTNEXTLINE(bugprone-use-after-move)
EXPECT_EQ(tuple.value<MoveOnly>().value, MoveOnly::kMoved);
// NOLINTNEXTLINE(bugprone-use-after-move)
EXPECT_TRUE(tuple.has_value<MoveOnly>()) << "moved value is still present";
}
TEST(OptionalTuple, DestructionOnScopeExit) {
Counter::Reset();
EXPECT_EQ(Counter::created, 0);
EXPECT_EQ(Counter::destroyed, 0);
{
pw::OptionalTuple<Counter, Counter> tuple;
EXPECT_EQ(tuple.emplace<0>(1).value, 1);
EXPECT_EQ(tuple.emplace<1>(2).value, 2);
EXPECT_EQ(Counter::created, 2);
EXPECT_EQ(Counter::destroyed, 0);
}
EXPECT_EQ(Counter::created, 2);
EXPECT_EQ(Counter::destroyed, 2);
}
TEST(OptionalTuple, DestructionOnReset) {
Counter::Reset();
pw::OptionalTuple<Counter, int> tuple;
tuple.emplace<0>(1);
EXPECT_EQ(Counter::created, 1);
EXPECT_EQ(Counter::destroyed, 0);
tuple.reset<0>();
EXPECT_EQ(Counter::created, 1);
EXPECT_EQ(Counter::destroyed, 1);
EXPECT_FALSE(tuple.has_value<0>());
// Resetting an empty slot should do nothing.
tuple.reset<0>();
EXPECT_EQ(Counter::destroyed, 1);
}
TEST(OptionalTuple, DestructionOnEmplace) {
Counter::Reset();
pw::OptionalTuple<Counter> tuple;
tuple.emplace<0>(1);
EXPECT_EQ(Counter::created, 1);
EXPECT_EQ(Counter::destroyed, 0);
tuple.emplace<0>(2);
EXPECT_EQ(Counter::created, 2);
EXPECT_EQ(Counter::destroyed, 1);
EXPECT_EQ(tuple.value<0>().value, 2);
}
TEST(OptionalTuple, MoveConstructor) {
Counter::Reset();
pw::OptionalTuple<Counter, Counter, Counter, Counter> src(
3, 4, pw::kTupleNull, pw::kTupleNull);
EXPECT_EQ(Counter::created, 2);
EXPECT_EQ(Counter::destroyed, 0);
EXPECT_EQ(Counter::moved, 0);
pw::OptionalTuple<Counter, Counter, Counter, Counter> dest(std::move(src));
EXPECT_EQ(Counter::created, 2);
EXPECT_EQ(Counter::moved, 2) << "moved constructed two Counters";
EXPECT_EQ(Counter::destroyed, 2) << "destroyed the moved-from Counters";
EXPECT_EQ(dest.value<0>().value, 3);
EXPECT_EQ(dest.value<1>().value, 4);
EXPECT_FALSE(dest.has_value<2>());
EXPECT_FALSE(dest.has_value<3>());
EXPECT_TRUE(src.empty()); // NOLINT(bugprone-use-after-move)
}
TEST(OptionalTuple, MoveAssignment) {
Counter::Reset();
pw::OptionalTuple<Counter, Counter, Counter, Counter> dest(
1, pw::kTupleNull, 2, pw::kTupleNull);
pw::OptionalTuple<Counter, Counter, Counter, Counter> src(
3, 4, pw::kTupleNull, pw::kTupleNull);
EXPECT_EQ(Counter::created, 4);
EXPECT_EQ(Counter::destroyed, 0);
EXPECT_EQ(Counter::moved, 0);
dest = std::move(src);
EXPECT_EQ(Counter::created, 4);
EXPECT_EQ(Counter::destroyed, 4);
EXPECT_EQ(Counter::moved, 2);
EXPECT_EQ(dest.value<0>().value, 3);
EXPECT_EQ(dest.value<1>().value, 4);
EXPECT_FALSE(dest.has_value<2>());
EXPECT_FALSE(dest.has_value<3>());
EXPECT_TRUE(src.empty()); // NOLINT(bugprone-use-after-move)
}
TEST(OptionalTuple, CopyConstructor) {
Counter::Reset();
pw::OptionalTuple<Counter, Counter, Counter, Counter> src(
3, 4, pw::kTupleNull, pw::kTupleNull);
EXPECT_EQ(Counter::created, 2);
EXPECT_EQ(Counter::destroyed, 0);
EXPECT_EQ(Counter::moved, 0);
pw::OptionalTuple<Counter, Counter, Counter, Counter> dest(src);
EXPECT_EQ(Counter::created, 4);
EXPECT_EQ(Counter::moved, 0);
EXPECT_EQ(Counter::destroyed, 0);
EXPECT_EQ(src.value<0>().value, 3);
EXPECT_EQ(src.value<1>().value, 4);
EXPECT_FALSE(src.has_value<2>());
EXPECT_FALSE(src.has_value<3>());
EXPECT_EQ(dest.value<0>().value, 3);
EXPECT_EQ(dest.value<1>().value, 4);
EXPECT_FALSE(dest.has_value<2>());
EXPECT_FALSE(dest.has_value<3>());
}
TEST(OptionalTuple, CopyAssignment) {
Counter::Reset();
pw::OptionalTuple<Counter, Counter, Counter, Counter> dest(
1, pw::kTupleNull, 2, pw::kTupleNull);
pw::OptionalTuple<Counter, Counter, Counter, Counter> src(
3, 4, pw::kTupleNull, pw::kTupleNull);
EXPECT_EQ(Counter::created, 4);
EXPECT_EQ(Counter::destroyed, 0);
EXPECT_EQ(Counter::moved, 0);
dest = src;
EXPECT_EQ(Counter::created, 6) << "Created two new copies";
EXPECT_EQ(Counter::destroyed, 2) << "Destroys all before copy";
EXPECT_EQ(Counter::moved, 0);
EXPECT_EQ(src.value<0>().value, 3);
EXPECT_EQ(src.value<1>().value, 4);
EXPECT_FALSE(src.has_value<2>());
EXPECT_FALSE(src.has_value<3>());
EXPECT_EQ(dest.value<0>().value, 3);
EXPECT_EQ(dest.value<1>().value, 4);
EXPECT_FALSE(dest.has_value<2>());
EXPECT_FALSE(dest.has_value<3>());
}
TEST(OptionalTuple, ValueOr_ByIndex) {
pw::OptionalTuple<int, int> tuple(100, pw::kTupleNull);
EXPECT_EQ(tuple.value_or<0>(200), 100);
EXPECT_EQ(tuple.value_or<1>(200), 200);
EXPECT_FALSE(tuple.has_value<1>());
}
TEST(OptionalTuple, ValueOr_ByType) {
pw::OptionalTuple<int, long> tuple(100, pw::kTupleNull);
EXPECT_EQ(tuple.value_or<int>(200), 100);
EXPECT_EQ(tuple.value_or<long>(500), 500);
EXPECT_FALSE(tuple.has_value<long>());
}
TEST(OptionalTuple, ValueOr_ByIndex_RvalueRefDefault) {
pw::OptionalTuple<int, MoveOnly> tuple(100, pw::kTupleNull);
MoveOnly moved_value = std::move(tuple).value_or<1>(MoveOnly(200));
EXPECT_EQ(moved_value.value, 200);
EXPECT_FALSE(tuple.has_value<1>()); // NOLINT(bugprone-use-after-move)
}
TEST(OptionalTuple, ValueOr_ByIndex_RvalueRefMoved) {
pw::OptionalTuple<int, MoveOnly> tuple(100, MoveOnly(42));
MoveOnly moved_value = std::move(tuple).value_or<1>(MoveOnly(200));
EXPECT_EQ(moved_value.value, 42);
// NOLINTNEXTLINE(bugprone-use-after-move)
EXPECT_TRUE(tuple.has_value<1>());
// NOLINTNEXTLINE(bugprone-use-after-move)
EXPECT_EQ(tuple.value<1>().value, MoveOnly::kMoved);
}
TEST(OptionalTuple, ValueOr_ByType_RvalueRefDefault) {
pw::OptionalTuple<int, MoveOnly> tuple(100, pw::kTupleNull);
MoveOnly moved_value = std::move(tuple).value_or<MoveOnly>(MoveOnly(200));
EXPECT_EQ(moved_value.value, 200);
EXPECT_FALSE(tuple.has_value<1>()); // NOLINT(bugprone-use-after-move)
}
TEST(OptionalTuple, ValueOr_ByType_RvalueRefMoved) {
pw::OptionalTuple<int, MoveOnly> tuple(100, MoveOnly(42));
MoveOnly moved_value = std::move(tuple).value_or<MoveOnly>(MoveOnly(200));
EXPECT_EQ(moved_value.value, 42);
// NOLINTNEXTLINE(bugprone-use-after-move)
EXPECT_TRUE(tuple.has_value<1>());
// NOLINTNEXTLINE(bugprone-use-after-move)
EXPECT_EQ(tuple.value<1>().value, MoveOnly::kMoved);
}
#if PW_CXX_STANDARD_IS_SUPPORTED(20)
constexpr pw::OptionalTuple<int, bool> kTuple(123, pw::kTupleNull);
static_assert(kTuple.count() == 1);
static_assert(kTuple.size() == 2);
static_assert(kTuple.value<int>() == 123);
static_assert(!kTuple.has_value<1>());
#endif // PW_CXX_STANDARD_IS_SUPPORTED(20)
static_assert(
std::is_same_v<std::tuple_element_t<0, pw::OptionalTuple<long>>, long>);
static_assert(
std::is_same_v<std::tuple_element_t<0, pw::OptionalTuple<int, bool, void*>>,
int>);
static_assert(
std::is_same_v<std::tuple_element_t<1, pw::OptionalTuple<int, bool, void*>>,
bool>);
static_assert(
std::is_same_v<std::tuple_element_t<2, pw::OptionalTuple<int, bool, void*>>,
void*>);
static_assert(std::tuple_size_v<pw::OptionalTuple<>> == 0);
static_assert(std::tuple_size_v<pw::OptionalTuple<bool>> == 1);
static_assert(std::tuple_size_v<pw::OptionalTuple<long long>> == 1);
static_assert(std::tuple_size_v<pw::OptionalTuple<bool, int>> == 2);
static_assert(std::tuple_size_v<pw::OptionalTuple<long, int, bool>> == 3);
} // namespace