Guide
Choosing Between std::optional and Sentinel Values in C++
Decide whether to use std::optional or a sentinel value for representing missing data in C++, weighing safety, storage, and domain constraints.
Published by Tasadduq Burney
10 Apr 2026, 23:16 UTC
3 min47.2K views0

Decision: Representing Absent Values in C++
When a function may not produce a result, you need a way to signal “no value” without resorting to error-codes or exceptions. Two common approaches in modern C++ are std::optional (available since C++17) and the sentinel-value technique, where a special reserved value (e.g., -1 for an int) indicates absence.
Constraints and Decision Factors
- Language version: Must support C++17 for
std::optional. - Type safety: Prefer a solution that the compiler can check to prevent accidental use of an "absent" value.
- Storage overhead: Determine if an extra byte or alignment padding is acceptable.
- Domain knowledge: Sentinel values only work if a reserved value is guaranteed never to appear in valid data.
Comparison Table
| Aspect | std::optional | Sentinel value |
|---|---|---|
| Required version | C++17 or later | Any version |
| Storage overhead | Small (typically 1 byte + padding) | None |
| Runtime check | Branch on has_value() |
Comparison to reserved value |
| Type safety | High (encoded in type) | Low (relies on convention) |
| Domain restrictions | None | Requires a non-valid reserved value |
| Readability | Self-documenting | Implicit |
Trade-offs
- Safety vs. Convenience:
std::optionalforces the caller to explicitly handle the empty case, reducing bugs. A sentinel can be ignored, leading to silent logic errors where a reserved value is treated as a legitimate result. - Storage Impact: For trivial types, the optional's extra boolean is often placed in existing padding. For non-trivial types, the size increase can be more significant due to alignment requirements. This should be verified using
sizeof. - Domain Assumptions: If the data domain naturally includes the sentinel (e.g., temperatures can be negative), the sentinel approach is unsafe unless a separate validity flag is added, which effectively replicates
std::optional.
Concrete Implementation
The following example compares two ways to implement a search function. The first uses std::optional; the second uses a sentinel (-1) assuming the vector contains only non-negative integers.
#include <vector>
#include <optional>
#include <iostream>
// Returns std::nullopt if not found, otherwise the index.
std::optional<std::size_t> find_optional(const std::vector<int>& data, int value)
{
for (std::size_t i = 0; i < data.size(); ++i)
{
if (data[i] == value)
{
return i;
}
}
return std::nullopt;
}
// Returns -1 if not found; assumes data never contains -1 as a valid index.
std::ptrdiff_t find_sentinel(const std::vector<int>& data, int value)
{
for (std::size_t i = 0; i < data.size(); ++i)
{
if (data[i] == value)
{
return static_cast<std::ptrdiff_t>(i);
}
}
return -1; // sentinel
}
int main()
{
std::vector<int> v = {5, 12, 7, 3};
int key = 7;
// Using optional
auto opt = find_optional(v, key);
if (opt)
{
std::cout << "Found at index " << *opt << '\n';
}
else
{
std::cout << "Not found (optional)\" << '\n';
}
// Using sentinel
auto idx = find_sentinel(v, key);
if (idx != -1)
{
std::cout << "Found at index " << idx << '\n';
}
else
{
std::cout << "Not found (sentinel)\" << '\n';
}
}
Validation Steps
- Compile with a C++17-compatible compiler:
g++ -std=c++17 -Wall -Wextra -O2 example.cpp -o example - Run the executable and verify that both branches print "Found at index 2" for the test data.
- Change
keyto a value not in the vector (e.g., 99) and confirm that the optional version prints "Not found (optional)" and the sentinel version prints "Not found (sentinel)". - To inspect overhead, use
sizeof(std::optional<std::size_t>)vssizeof(std::size_t)to observe the memory footprint increase.
When to Prefer the Sentinel Approach
- You are constrained to a pre-C++17 compiler.
- The domain guarantees a value that can never appear (e.g., file descriptors where
-1is reserved). - You have verified that the extra storage or branch of
std::optionalis a measurable bottleneck in a critical hot path.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.