C/C++ API Reference
Loading...
Searching...
No Matches
optional_tuple.h
1// Copyright 2025 The Pigweed Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not
4// use this file except in compliance with the License. You may obtain a copy of
5// the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12// License for the specific language governing permissions and limitations under
13// the License.
14#pragma once
15
16#include <cstddef>
17#include <cstdint>
18#include <tuple>
19#include <type_traits>
20#include <utility>
21
22#include "lib/stdcompat/type_traits.h"
23#include "pw_assert/assert.h"
24#include "pw_containers/bitset.h"
25#include "pw_polyfill/language_feature_macros.h"
26
27namespace pw {
28
29template <typename... Types>
30class OptionalTuple;
31
32namespace containers::internal {
33
34// Empty tag type to indicate that an `pw::OptionalTuple` element is not set.
35// Equivalent to `std::nullopt_t`. Use `pw::kTupleNull` to refer to a value of
36// this type.
37//
38// This type is for the exclusive use of only be used by Pigweed containers!
40 public:
41 static constexpr OptionalTupleNullPlaceholder InternalUseOnly() { return {}; }
42
43 private:
44 constexpr OptionalTupleNullPlaceholder() = default;
45};
46
47struct Empty {};
48
49// Untagged optional type for use in OptionalTuple.
50template <typename T>
51union Maybe {
52 explicit constexpr Maybe(OptionalTupleNullPlaceholder) : none{} {}
53
54 template <typename... Args>
55 constexpr Maybe(Args&&... args) : value(std::forward<Args>(args)...) {}
56
57 // A destructor is required for types with non-trivial destructors.
59
60 Empty none;
61 T value;
62};
63
64template <typename T, typename First, typename... Other>
65constexpr size_t FindFirstIndexOf() {
66 if constexpr (std::is_same_v<T, First>) {
67 return 0;
68 } else if constexpr (sizeof...(Other) > 0u) {
69 return 1 + FindFirstIndexOf<T, Other...>();
70 } else {
71 static_assert(sizeof...(Other) != 0u,
72 "The type is not present in the type parameter pack");
73 }
74}
75
76template <typename T>
77struct IsOptionalTuple : std::false_type {};
78
79template <typename... Types>
80struct IsOptionalTuple<OptionalTuple<Types...>> : std::true_type {};
81
82} // namespace containers::internal
83
85
95inline constexpr auto kTupleNull =
96 containers::internal::OptionalTupleNullPlaceholder::InternalUseOnly();
97
111template <typename... Types>
113 private:
114 template <typename T>
115 static constexpr size_t TypeToIndex();
116
117 template <size_t kIndex>
118 using Element = std::tuple_element_t<kIndex, std::tuple<Types...>>;
119
120 template <typename T>
121 using ElementByType = Element<TypeToIndex<T>()>;
122
123 public:
125 constexpr OptionalTuple() : tuple_(kToTupleNull<Types>..., Bits{}) {}
126
130 template <
131 typename... Args,
132 typename = std::enable_if_t<
133 (std::is_constructible_v<containers::internal::Maybe<Types>, Args> &&
134 ...) &&
135 (sizeof...(Types) != 1 || (!containers::internal::IsOptionalTuple<
136 cpp20::remove_cvref_t<Args>>::value &&
137 ...))>>
138 constexpr OptionalTuple(Args&&... args)
139 : tuple_(
140 std::forward<Args>(args)...,
142 !std::is_same_v<
143 cpp20::remove_cvref_t<Args>,
145
147 constexpr OptionalTuple(const OptionalTuple& other)
148 : tuple_(kToTupleNull<Types>..., Bits{}) {
149 UninitializedCopyFrom(other, kAllIdx);
150 }
151
153 constexpr OptionalTuple& operator=(const OptionalTuple& other) {
154 DestroyAll(kAllIdx); // To simplify the copy, first destroy all elements.
155 UninitializedCopyFrom(std::move(other), kAllIdx);
156 return *this;
157 }
158
160 constexpr OptionalTuple(OptionalTuple&& other)
161 : tuple_(kToTupleNull<Types>..., Bits{}) {
162 UninitializedMoveFrom(other, kAllIdx);
163 }
164
167 DestroyAll(kAllIdx); // To simplify the move, first destroy all elements.
168 UninitializedMoveFrom(other, kAllIdx);
169 return *this;
170 }
171
172 // Converting copy/move constructors are not yet implemented.
173
175 PW_CONSTEXPR_CPP20 ~OptionalTuple() { DestroyAll(kAllIdx); }
176
177 // swap() is not yet implemented.
178
180 constexpr bool empty() const { return active().none(); }
181
183 constexpr size_t count() const { return active().count(); }
184
187 constexpr size_t size() const { return sizeof...(Types); }
188
190 template <size_t kIndex>
191 constexpr bool has_value() const {
192 return active().template test<kIndex>();
193 }
194
196 template <typename T>
197 constexpr bool has_value() const {
198 return has_value<TypeToIndex<T>()>();
199 }
200
203 template <size_t kIndex>
204 constexpr Element<kIndex>& value() & {
205 PW_ASSERT(has_value<kIndex>());
206 return RawValue<kIndex>();
207 }
208
211 template <size_t kIndex>
212 constexpr const Element<kIndex>& value() const& {
213 PW_ASSERT(has_value<kIndex>());
214 return RawValue<kIndex>();
215 }
216
219 template <size_t kIndex>
220 constexpr Element<kIndex>&& value() && {
221 PW_ASSERT(has_value<kIndex>());
222 return std::move(RawValue<kIndex>());
223 }
224
227 template <size_t kIndex>
228 constexpr const Element<kIndex>&& value() const&& {
229 PW_ASSERT(has_value<kIndex>());
230 return std::move(RawValue<kIndex>());
231 }
232
235 template <typename T>
236 constexpr ElementByType<T>& value() & {
237 return value<TypeToIndex<T>()>();
238 }
239
242 template <typename T>
243 constexpr const ElementByType<T>& value() const& {
244 return value<TypeToIndex<T>()>();
245 }
246
249 template <typename T>
250 constexpr ElementByType<T>&& value() && {
251 return std::move(value<TypeToIndex<T>()>());
252 }
253
256 template <typename T>
257 constexpr const ElementByType<T>&& value() const&& {
258 return std::move(value<TypeToIndex<T>()>());
259 }
260
263 template <size_t kIndex, int... kExplicitGuard, typename U>
264 constexpr Element<kIndex> value_or(U&& default_value) const& {
265 return has_value<kIndex>()
266 ? RawValue<kIndex>()
267 : static_cast<Element<kIndex>>(std::forward<U>(default_value));
268 }
269
272 template <typename T, int... kExplicitGuard, typename U>
273 constexpr ElementByType<T> value_or(U&& default_value) const& {
274 return value_or<TypeToIndex<T>()>(std::forward<U>(default_value));
275 }
276
279 template <size_t kIndex, int... kExplicitGuard, typename U>
280 constexpr Element<kIndex> value_or(U&& default_value) && {
281 return has_value<kIndex>()
282 ? std::move(RawValue<kIndex>())
283 : static_cast<Element<kIndex>>(std::forward<U>(default_value));
284 }
285
288 template <typename T, int... kExplicitGuard, typename U>
289 constexpr ElementByType<T> value_or(U&& default_value) && {
290 return std::move(*this).template value_or<TypeToIndex<T>()>(
291 std::forward<U>(default_value));
292 }
293
298 template <size_t kIndex, int... kExplicitGuard, typename... Args>
299 constexpr Element<kIndex>& emplace(Args&&... args) {
300 DestroyIfActive<kIndex>();
301 active().template set<kIndex>();
302 return *new (&RawValue<kIndex>())
303 Element<kIndex>(std::forward<Args>(args)...);
304 }
305
310 template <typename T, int... kExplicitGuard, typename... Args>
311 constexpr ElementByType<T>& emplace(Args&&... args) {
312 return emplace<TypeToIndex<T>()>(std::forward<Args>(args)...);
313 }
314
316 template <size_t kIndex>
317 constexpr void reset() {
318 DestroyIfActive<kIndex>();
319 active().template reset<kIndex>();
320 }
321
323 template <typename T>
324 constexpr void reset() {
325 return reset<TypeToIndex<T>()>();
326 }
327
328 private:
329 static constexpr auto kAllIdx = std::make_index_sequence<sizeof...(Types)>{};
330
331 // Used to expand N types to N uses of kTupleNull.
332 template <typename>
333 static constexpr auto kToTupleNull = kTupleNull;
334
335 template <size_t... kIndices>
336 constexpr void DestroyAll(std::index_sequence<kIndices...>) {
337 (DestroyIfActive<kIndices>(), ...);
338 }
339
340 template <size_t kIndex>
341 constexpr void DestroyIfActive() {
342 if (has_value<kIndex>()) {
343 std::destroy_at(&RawValue<kIndex>());
344 }
345 }
346
347 template <size_t... kIndices>
348 constexpr void UninitializedCopyFrom(const OptionalTuple& other,
349 std::index_sequence<kIndices...>) {
350 (CopyElement<kIndices>(other), ...);
351 active() = other.active();
352 }
353
354 template <size_t kIndex>
355 constexpr void CopyElement(const OptionalTuple& other) {
356 if (other.has_value<kIndex>()) {
357 new (&RawValue<kIndex>()) Element<kIndex>(other.RawValue<kIndex>());
358 }
359 }
360
361 template <size_t... kIndices>
362 constexpr void UninitializedMoveFrom(OptionalTuple& other,
363 std::index_sequence<kIndices...>) {
364 (MoveElement<kIndices>(other), ...);
365 active() = other.active();
366 other.active().reset();
367 }
368
369 template <size_t kIndex>
370 constexpr void MoveElement(OptionalTuple& other) {
371 if (other.has_value<kIndex>()) {
372 new (&RawValue<kIndex>())
373 Element<kIndex>(std::move(other.RawValue<kIndex>()));
374 std::destroy_at(&other.RawValue<kIndex>());
375 }
376 }
377
378 template <size_t kIndex>
379 constexpr Element<kIndex>& RawValue() {
380 static_assert(kIndex < sizeof...(Types));
381 return std::get<kIndex>(tuple_).value;
382 }
383
384 template <size_t kIndex>
385 constexpr const Element<kIndex>& RawValue() const {
386 static_assert(kIndex < sizeof...(Types));
387 return std::get<kIndex>(tuple_).value;
388 }
389
390 private:
391 using Bits = BitSet<sizeof...(Types)>;
392
393 constexpr Bits& active() { return std::get<sizeof...(Types)>(tuple_); }
394 constexpr const Bits& active() const {
395 return std::get<sizeof...(Types)>(tuple_);
396 }
397
398 // Include the bitset as the final tuple element for more efficient packing.
399 std::tuple<containers::internal::Maybe<Types>..., Bits> tuple_;
400};
401
403
404template <typename... Types>
405template <typename T>
406constexpr size_t OptionalTuple<Types...>::TypeToIndex() {
407 if constexpr ((std::is_same_v<T, Types> + ...) == 1) {
408 return containers::internal::FindFirstIndexOf<T, Types...>();
409 } else { // Use if constexpr and return 0 to clean up error messages.
410 static_assert(
411 (std::is_same_v<T, Types> + ...) == 1,
412 "To access an item by type, the type must appear exactly once in the "
413 "OptionalTuple. Specify the item's index instead.");
414 return 0;
415 }
416}
417
418} // namespace pw
419
420// Specialize std::tuple_element for pw::OptionalTuple.
421template <typename First, typename... Other>
422struct std::tuple_element<0, ::pw::OptionalTuple<First, Other...>> {
423 using type = First;
424};
425
426template <std::size_t kIndex, typename First, typename... Other>
427struct std::tuple_element<kIndex, ::pw::OptionalTuple<First, Other...>>
428 : public std::tuple_element<kIndex - 1, ::pw::OptionalTuple<Other...>> {};
429
430// Specialize std::tuple_size for pw::OptionalTuple.
431template <typename... Types>
432struct std::tuple_size<::pw::OptionalTuple<Types...>>
433 : std::integral_constant<std::size_t, sizeof...(Types)> {};
Definition: bitset.h:34
static constexpr BitSet LittleEndian(Args... bits_least_to_most_significant)
Definition: bitset.h:58
Definition: optional_tuple.h:112
constexpr OptionalTuple & operator=(OptionalTuple &&other)
Move assigns this from another OptionalTuple.
Definition: optional_tuple.h:166
constexpr Element< kIndex > value_or(U &&default_value) &&
Definition: optional_tuple.h:280
constexpr const ElementByType< T > & value() const &
Definition: optional_tuple.h:243
constexpr ElementByType< T > value_or(U &&default_value) &&
Definition: optional_tuple.h:289
~OptionalTuple()
Destroys the OptionalTuple and all its active elements.
Definition: optional_tuple.h:175
constexpr const ElementByType< T > && value() const &&
Definition: optional_tuple.h:257
constexpr bool empty() const
Checks if the OptionalTuple contains no active elements.
Definition: optional_tuple.h:180
constexpr ElementByType< T > && value() &&
Definition: optional_tuple.h:250
constexpr ElementByType< T > & value() &
Definition: optional_tuple.h:236
constexpr Element< kIndex > value_or(U &&default_value) const &
Definition: optional_tuple.h:264
constexpr bool has_value() const
Checks if the element at kIndex has a value.
Definition: optional_tuple.h:191
constexpr Element< kIndex > && value() &&
Definition: optional_tuple.h:220
constexpr size_t size() const
Definition: optional_tuple.h:187
constexpr const Element< kIndex > && value() const &&
Definition: optional_tuple.h:228
constexpr OptionalTuple & operator=(const OptionalTuple &other)
Copy assigns this from another OptionalTuple.
Definition: optional_tuple.h:153
constexpr OptionalTuple(OptionalTuple &&other)
Move constructs from another OptionalTuple.
Definition: optional_tuple.h:160
constexpr void reset()
Resets (clears) the value at the specified index, if any.
Definition: optional_tuple.h:317
constexpr Element< kIndex > & value() &
Definition: optional_tuple.h:204
constexpr ElementByType< T > & emplace(Args &&... args)
Definition: optional_tuple.h:311
constexpr OptionalTuple(const OptionalTuple &other)
Copy constructs from another OptionalTuple.
Definition: optional_tuple.h:147
constexpr OptionalTuple(Args &&... args)
Definition: optional_tuple.h:138
constexpr OptionalTuple()
Default constructs an OptionalTuple with all elements unset.
Definition: optional_tuple.h:125
constexpr ElementByType< T > value_or(U &&default_value) const &
Definition: optional_tuple.h:273
constexpr Element< kIndex > & emplace(Args &&... args)
Definition: optional_tuple.h:299
constexpr const Element< kIndex > & value() const &
Definition: optional_tuple.h:212
constexpr size_t count() const
Returns the number of active elements in the OptionalTuple.
Definition: optional_tuple.h:183
constexpr auto kTupleNull
Definition: optional_tuple.h:95
#define PW_CONSTEXPR_CPP20
Definition: language_feature_macros.h:27
The Pigweed namespace.
Definition: alignment.h:27
Definition: optional_tuple.h:47
Definition: optional_tuple.h:77
Definition: optional_tuple.h:51