Skip to content

Wiki

std::optional

About 9 min read

Existing English translations were reused. 5 current source blocks remain in Simplified Chinese, without triggering paid translation.

1.What problem does this section solve?

When a function returns a result, how is "no result" represented?

Common approaches each have their own drawbacks:

  • Return -1 or nullptr: semantics are unclear, and -1 may be a valid value.
  • Return bool + output parameter: the code is verbose and requires declaring a variable first.
  • Throw an exception: exceptions have overhead, and "not found" is often not an exceptional case.

std::optional<T>elegantly expresses the semantics of "may have a value, or may not have a value".

2.What is this feature?

std::optional<T> is a template class that either contains a value of type T or is empty (no value). It makes the information "may have no value" part of the type, and the compiler checks it for you.

3.C++ Standard Edition

C++17

4.Required header files

#include <optional>

5.Basic syntax

std::optional<T> opt;                    // 默认空
std::optional<T> opt = value;            // 有值
std::optional<T> opt = std::nullopt;     // 显式空

// 检查是否有值
if (opt.has_value()) { ... }
if (opt) { ... }                         // 隐式转 bool

// 获取值
T val = opt.value();                     // 若无值抛异常
T val = *opt;                            // 若无值是未定义行为
T val = opt.value_or(default);           // 若无值返回默认值

6.Common Usage

操作 说明
opt.has_value() 是否有值
opt.value() 获取值(无值抛 std::bad_optional_access)
*opt 获取值(无值行为未定义,先检查!)
opt.value_or(default) 获取值,无值返回默认值
opt = std::nullopt 清空
opt.emplace(args...) 原地构造值

7.Example code

7.1.Example 1: Using optional to indicate "may not be found"

#include <iostream>
#include <optional>
#include <string>
#include <vector>

// 在 vector 中查找元素,返回位置(可能找不到)
std::optional<size_t> find_index(const std::vector<std::string>& v,
                                  const std::string& target)
{
    for (size_t i = 0; i < v.size(); ++i)
    {
        if (v[i] == target)
        {
            return i;  // 找到了
        }
    }
    return std::nullopt;  // 没找到
}

int main()
{
    std::vector<std::string> names = {"Alice", "Bob", "Charlie"};

    auto idx = find_index(names, "Bob");
    if (idx)
    {
        std::cout << "Found Bob at index " << *idx << "\n";
    }

    auto idx2 = find_index(names, "David");
    if (!idx2)
    {
        std::cout << "David not found\n";
    }

    // 使用 value_or 提供默认值
    std::cout << "Result: " << idx.value_or(999) << "\n";
    std::cout << "Result2: " << idx2.value_or(999) << "\n";

    return 0;
}

Running result:

Found Bob at index 1
David not found
Result: 1
Result2: 999

7.2.Example 2: Building on Example 1, use optional to represent a "conversion that may fail"

#include <iostream>
#include <optional>
#include <string>

// 字符串转整数,失败返回 nullopt
std::optional<int> to_int(const std::string& s)
{
    try
    {
        size_t pos;
        int val = std::stoi(s, &pos);
        if (pos == s.size())  // 整个字符串都被转换了
        {
            return val;
        }
    }
    catch (...) {}
    return std::nullopt;
}

int main()
{
    auto r1 = to_int("42");
    auto r2 = to_int("3.14");  // 带小数点
    auto r3 = to_int("hello"); // 不是数字

    std::cout << "\"42\" -> " << r1.value_or(-1) << "\n";
    std::cout << "\"3.14\" -> " << r2.value_or(-1) << "\n";
    std::cout << "\"hello\" -> " << r3.value_or(-1) << "\n";

    // 用 if 检查
    if (auto r = to_int("100"); r)
    {
        std::cout << "100 * 2 = " << (*r * 2) << "\n";
    }

    return 0;
}

Running result:

"42" -> 42
"3.14" -> -1
"hello" -> -1
100 * 2 = 200

Here, thestd::stoifunction's conversion rule is: starting from the beginning of the string (which must begin with a digit), parse an integer as much as possible; as long as a part can be parsed, stoi itself is considered successful. For example:

  • Convert "123" string, the result is successful, which is 123.
  • Convert "123abc" string, the result is successful, which is 123.
  • Convert "3.14" string, the result is successful, which is 3.

Theposparameters of this function indicatestd::stoiwhich position in the string was parsed, that is, the position of the first character that was not converted. For example:

  • Convert"123abc"string,valis 123, butposis 3. (Index starts from 0, as shown in the figure below)
123abc
012345
   ↑
   pos = 3
  • 转换"3.14"字符串,val是3,但pos是1。(序号从0开始,如下图)
3.14
0123
 ↑
 pos = 1

Here, thepos == s.size() is not valid, so returnstd::nullopt.

7.3.Example 3: Based on Example 2, use optional as a struct member (a field that "may be absent")

#include <iostream>
#include <optional>
#include <string>

struct Person
{
    std::string name;
    int age;
    std::optional<std::string> nickname;  // 可能没有昵称
    std::optional<int> score;              // 成绩可能尚未录入
};

void print_person(const Person& p)
{
    std::cout << "Name: " << p.name << "\n";
    std::cout << "Age: " << p.age << "\n";

    std::cout << "Nickname: " << p.nickname.value_or("(none)") << "\n";
    std::cout << "Score: " << p.score.value_or(-1) << "\n";

    std::cout << "---\n";
}

int main()
{
    Person p1{"Alice", 20, "Ali", 95};
    Person p2{"Bob", 22, std::nullopt, std::nullopt};  // 没有昵称和成绩

    print_person(p1);
    print_person(p2);

    return 0;
}

Running result:

Name: Alice
Age: 20
Nickname: Ali
Score: 95
---
Name: Bob
Age: 22
Nickname: (none)
Score: -1
---

8.Operation result

See the "run results" for each example above.

9.Key syntax explanation in the example

Example What was discussed Newly introduced syntax Why it is written this way Notes
Example 1 optional returns the lookup result return i; / return std::nullopt;、if(opt)、*opt、value_or() optional makes "no value" a normal return, with clear semantics Before dereferencing *opt , ensure there is a value
Example 2 optional indicates conversion failure try/catch wrapping + optional Convert exceptions into an empty optional, making it easier for callers to handle You can also use std::from_chars (C++17), which is more efficient
Example 3 optional as a member variable std::optional<std::string> member For "optional fields", using optional is clearer than using magic values (such as -1) optional itself takes extra space (size of T + bool + padding)

10.optional is suitable for "may not have"; it is not suitable for "there are many error details".

optional It expresses: this value may or may not exist. It is not responsible for explaining "why it failed".

场景 推荐
查找名字,可能找不到 std::optional<size_t>
配置项可能没填 std::optional<std::string>
字符串转数字,只关心成不成功 std::optional<int>
打开文件失败,需要知道权限/路径/格式错误 错误码、异常、或 expected 类工具
网络请求失败,需要错误类型和错误消息 错误对象,不要只用 optional

10.1.Example 4: The difference between optional and error information

#include <iostream>
#include <optional>
#include <string>

// optional 表示“可能有值,也可能没有值”的结果。
std::optional<int> parse_port_simple(const std::string& text)
{
    try
    {
        int port = std::stoi(text);
        if (port >= 0 && port <= 65535)
        {
            return port;
        }
    }
    catch (...) {}
    return std::nullopt;
}

struct ParseResult
{
    bool ok;
    int value;
    std::string error;
};

ParseResult parse_port_with_error(const std::string& text)
{
    try
    {
        int port = std::stoi(text);
        if (port < 0 || port > 65535)
        {
            return {false, 0, "port out of range"};
        }
        return {true, port, ""};
    }
    catch (...)
    {
        return {false, 0, "not a number"};
    }
}

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    auto simple = parse_port_simple("70000");
    std::cout << "simple ok? " << (simple ? "yes" : "no") << "\n";

    auto detailed = parse_port_with_error("70000");
    if (!detailed.ok)
    {
        std::cout << "error: " << detailed.error << "\n";
    }

    return 0;
}

Running result:

simple ok? no
error: port out of range

If the caller only cares about "whether there is a value,"optional is very suitable; if the caller needs to know the reason for failure, a separate optional is not enough.

11.Common Errors

Error 1: Do not check and directly *opt

std::optional<int> opt;
std::cout << *opt;  // ❌ 未定义行为!opt 为空

Correct approach: first if (opt) check, or use opt.value_or(default).

错误 2:把 std::nullopt 和 nullptr 混淆

std::optional<int> opt = nullptr;  // ❌ 编译错误!应该用 std::nullopt

Correct approach:opt = std::nullopt;

Error 3: Forgetting that value() will throw an exception when there is no value

std::optional<int> opt;
std::cout << opt.value();  // ❌ 抛出 std::bad_optional_access

The correct approach: use value_or(default) or check first.

12.Usage Recommendations

  1. 函数可能"没有结果"时返回 std::optional<T>:比返回 -1/nullptr 更安全。
  2. 结构体中"可选字段"用 optional:比 magic number 语义更清晰。
  3. 默认用 value_or() 而不是 *opt:value_or() 更安全。
  4. std::optional 有额外空间开销:等于 sizeof(T) + sizeof(bool) + padding,小对象影响不大。
  5. C++23 引入了 std::optional 的 monadic 操作:.and_then(), .transform(), .or_else() 链式调用更优雅。
  6. 需要错误详情时别只用 optional:optional 只表达有没有值,不表达为什么没有。

13.Summary

  • std::optional<T> Either contains a T, or is empty.
  • Resolves the semantic problem that a function "may have no result", replacing magic value.
  • Use has_value() or if(opt) to check, and use value_or(default) to safely get the value.
  • Can be used for function return values, struct members, and function parameters (but optional is not recommended for parameters).