C++의 std::expected는 성공 값 또는 오류 값을 한 객체에 담는 C++23 유틸리티이며, 예외를 던지지 않고 실패를 반환할 때 씁니다. 호출자는 성공 여부를 확인한 뒤 값 또는 오류를 명시적으로 처리할 수 있습니다.
이 글은 cppreference의 std::expected 문서를 2026-08-30 기준으로 정리한 일반 설명이며, 표준 버전과 구현에 따라 세부 동작은 달라질 수 있습니다.
std::expected는 무엇인가?

한 줄 답: 이 타입은 기대한 값 T 또는 예상하지 못한 값 E 중 하나를 나타내며, 값도 오류도 없는 제3의 상태를 갖지 않습니다.
<expected> 헤더에 정의된 template< class T, class E > class expected;는 C++23부터 제공됩니다. 주 템플릿은 성공 값 또는 오류 값을 객체 내부의 저장 공간에 보관하며, 항상 둘 중 하나를 유지합니다.
value_type은 성공 값의 형식 T, error_type은 오류 값의 형식 E를 나타냅니다. unexpected_type은 std::unexpected<E>이며 오류 상태를 담는 형식이고, rebind<U>는 오류 형식을 유지하면서 성공 값 형식만 U로 바꾼 별칭입니다.
std::unexpected<E>를 단일 인자로 받는 생성자는 오류 값을 담은 상태를 만듭니다. 따라서 실패를 반환하는 코드는 다음처럼 작성할 수 있습니다.
return std::unexpected(parse_error::invalid_input);
성공 값이 없는 작업은 void 부분 특수화를 사용하여 성공한 void 상태 또는 오류 값을 나타낼 수 있습니다. 참조형·함수형·std::unexpected 특수화로는 인스턴스화할 수 없으며, T와 E도 저장과 소멸에 관한 형식 제약을 만족해야 합니다.
optional과 무엇이 다른가?

한 줄 답: optional은 값의 부재를 표현하고, 이 타입은 실패 사유가 담긴 오류 값까지 함께 표현합니다.
optional이 객체를 포함하지 않을 수 있는 래퍼라면, std::expected는 성공 값 또는 오류 값 중 하나를 보관합니다. 따라서 값이 없는지만 알면 되는 경우와 실패 이유를 호출자에게 전달해야 하는 경우를 구분합니다.
성공과 실패는 어떻게 꺼내나?

한 줄 답: has_value() 또는 if (result)로 성공 여부를 먼저 확인하고, 성공이면 값을, 실패면 오류 값을 꺼냅니다.
has_value()와 명시적 operator bool()은 객체가 성공 값을 나타내는지 확인합니다. 반환값이 true이면 성공 상태이고, false이면 이 타입이 never valueless이므로 오류 값 상태입니다.
value()는 성공 값에 대한 참조를 반환합니다. 실패 상태에서 호출하면 오류 값의 사본을 담은 std::bad_expected_access<std::decay_t<E>>를 던지며, void 부분 특수화에서는 성공 시 반환할 값이 없습니다.
error()는 실패 상태의 오류 값에 접근합니다. 성공 상태에서 호출하면 C++26 전까지 정의되지 않은 동작이므로, 성공 여부를 확인하지 않고 호출하면 안 됩니다.
value_or(default_value)는 성공 값이 있으면 그 값을, 없으면 전달한 기본값을 반환합니다. error_or는 오류 값이 있으면 그것을, 성공 상태면 대체 오류 값을 반환하며, void 부분 특수화에는 이 멤버들이 없습니다.
공식 parse_number 예시의 핵심 흐름은 다음과 같습니다. 실패할 때 std::unexpected를 반환하고, 호출부에서 has_value()로 분기한 뒤 성공 값은 operator*로, 실패 원인은 error()로 확인합니다.
auto parse_number(std::string_view& str)
-> std::expected<double, parse_error>
{
const char* begin = str.data();
char* end;
double retval = std::strtod(begin, &end);
if (begin == end)
return std::unexpected(parse_error::invalid_input);
if (std::isinf(retval))
return std::unexpected(parse_error::overflow);
str.remove_prefix(end - begin);
return retval;
}
if (const auto num = parse_number(input); num.has_value())
consume(*num);
else
handle(num.error());
실패 상태에서 검사 없이 operator*나 operator->를 사용하면 정의되지 않은 동작이 될 수 있습니다. 같은 상황에서 value()는 bad_expected_access 예외를 던지고, value_or는 지정한 기본값을 사용합니다.
and_then, transform, or_else, transform_error도 C++23에서 제공되며 성공·실패 흐름을 연결할 때 사용할 수 있습니다. 각 멤버의 반환 규약은 사용하려는 함수와 함께 확인해야 합니다.
예외 대신 이 타입을 쓰는 이유는 무엇인가?

한 줄 답: 실패를 E형 반환값으로 전달하므로 호출자가 같은 반환 경로에서 성공과 오류를 분기할 수 있습니다.
has_value() 또는 operator bool()로 분기하면 실패가 스택을 풀어 올리는 예외가 아니라 호출자가 확인하는 값으로 전달됩니다. 실패 상태에서는 error()로 원인을 읽거나 error_or 같은 대체 경로를 선택할 수 있습니다.
다만 “예외 없이”라는 표현은 이 반환을 값으로 확인하는 흐름을 뜻합니다. 실패 상태에서 value()를 호출하면 std::bad_expected_access가 발생하므로, value()까지 예외를 사용하지 않는다는 뜻은 아닙니다.
이 타입은 항상 성공 값 또는 오류 값 중 하나를 가지므로 상태를 명시적으로 모델링할 수 있습니다. 그렇다고 모든 예외를 대체하거나 언제나 더 빠르다고 단정할 수는 없으며, 오류를 값으로 전달하는 API가 적합한지는 호출 규약에 따라 판단해야 합니다.
C++ 개념을 별도로 점검하려면 C++ 기술면접 질문 10가지를 참고할 수 있으며, 해당 글의 내용은 여기서 다시 다루지 않습니다.
FAQ
한 줄 답: 성공 여부 확인, 오류 접근의 전제, value()의 예외 조건을 구분하면 이 반환을 안전하게 사용할 수 있습니다.
실패를 예외 없이 반환하려면 무엇을 사용합니까?
성공 값과 오류 값을 함께 표현하는 이 반환을 사용하고, 호출부에서 성공 여부를 확인한 뒤 오류 값을 처리합니다.
has_value()가 true일 때 error()를 호출하면 어떻게 됩니까?
성공 상태에서 error()를 호출하면 C++26 전까지 정의되지 않은 동작이므로, 호출 전에 실패 상태인지 확인해야 합니다.
value()는 예외를 던집니까?
성공 상태에서는 값의 참조를 반환하지만, 실패 상태에서는 std::bad_expected_access를 던집니다.
optional과 이 타입은 언제 구분해 사용합니까?
값의 부재만 표현하면 전자를 사용하고, 실패 사유를 오류 값으로 함께 전달해야 하면 후자를 사용합니다.
출처
한 줄 답: 상태 모델과 멤버의 사전조건은 cppreference의 C++23 expected 및 관련 문서에서 확인할 수 있습니다.
- 개요·never valueless·공식 예시: expected 개요
- 오류 값을 나타내는 형식:
std::unexpected - 생성자:
expected생성자 - 성공 여부 확인:
has_value()와operator bool - 값 접근과 예외:
value()와bad_expected_access - 오류 접근:
error() - 대체 값 접근:
value_or - 흐름 연결:
and_then와transform - 값 부재를 표현하는 래퍼:
optional
std::expected is a C++23 utility that stores either a success value or an error in one object. It is designed for returning failure without throwing exceptions, so callers can inspect the result and handle the value or error explicitly.
This article provides a general explanation based on cppreference's std::expected documentation as of 2026-08-30. Specific behavior can vary with the standard version and the implementation.
What is C++ std::expected?
Short answer: It represents exactly one of an expected value T or an unexpected value E; it has no third state where neither a value nor an error is present.
The <expected> header provides template< class T, class E > class expected; starting in C++23. The primary template stores either the success value or the error value in its internal storage and always holds one of them.
value_type names the success-value type T, while error_type names the error type E. unexpected_type is std::unexpected<E>, the type used to carry the error state. rebind<U> is an alias that keeps the error type and changes only the success-value type to U.
A constructor that takes a single std::unexpected<E> argument creates an error state. A function can therefore return a failure like this:
return std::unexpected(parse_error::invalid_input);
For an operation with no success value, the void partial specialization represents either successful void completion or an error. The type cannot be instantiated with a reference type, a function type, or an std::unexpected specialization, and both T and E must satisfy the type requirements for storage and destruction.
How is C++ std::expected different from std::optional?
Short answer: std::optional expresses that a value is absent, while C++ std::expected also carries an error value that explains the exact reason for the failure.
An optional is a wrapper that may contain no object. std::expected instead stores either a success value or an error value, so the choice depends on whether absence alone is enough or if the caller must receive the specific reason for failure.
How to read success values and errors from C++ std::expected?
Short answer: Check success first with has_value() or if (result), then read the success value, or access the error if it failed.
has_value() and the explicit operator bool() test whether the object represents a success value. true means success; false means the error state because this type is never valueless.
value() returns a reference to the success value. If it is called in the failure state, it throws std::bad_expected_access<std::decay_t<E>> containing a copy of the error value. The void partial specialization has no value to return on success.
error() accesses the error value in the failure state. Calling it in the success state is undefined behavior before C++26, so always check the state first.
value_or(default_value) returns the success value when present and the supplied default otherwise. error_or returns the error when present and a fallback error in the success state; these members are not available in the void partial specialization.
The core flow of the official parse_number example is to return std::unexpected on failure, branch with has_value() at the call site, read the success value through operator*, and inspect the failure reason with error().
auto parse_number(std::string_view& str)
-> std::expected<double, parse_error>
{
const char* begin = str.data();
char* end;
double retval = std::strtod(begin, &end);
if (begin == end)
return std::unexpected(parse_error::invalid_input);
if (std::isinf(retval))
return std::unexpected(parse_error::overflow);
str.remove_prefix(end - begin);
return retval;
}
if (const auto num = parse_number(input); num.has_value())
consume(*num);
else
handle(num.error());
Using operator* or operator-> in a failure state without checking can be undefined behavior. In the same situation, value() throws bad_expected_access, while value_or uses the specified default.
and_then, transform, or_else, and transform_error are also available in C++23 and can connect success and failure flows. Check the return contract of each member together with the function you plan to use.
Why use C++ std::expected instead of exceptions?
Short answer: It passes failure as a return value of type E, allowing the caller to explicitly branch between success and error along the same standard return path.
Branching with has_value() or operator bool() makes failure a value checked by the caller instead of an exception that unwinds the stack. In the failure state, the caller can read the reason with error() or choose a fallback such as error_or.
However, “without exceptions” describes a flow where the returned result is checked as a value. Calling value() in the failure state still produces a std::bad_expected_access exception, so it does not mean that value() itself is exception-free.
Because the type always contains either a success value or an error, it models its state explicitly. This does not make it a replacement for every exception or guarantee that it is always faster; whether a value-returning error API is appropriate depends on your calling convention.
For a separate review of C++ concepts, see 10 C++ Technical Interview Questions; that article’s content is not covered again here.
What are common questions about C++ std::expected?
Short answer: Safe usage depends on distinguishing the success check, the precondition for accessing the error, and the exception condition of value().
How can you return failure without throwing in C++?
Use std::expected to represent the success value and error together, then check the state at the call site before handling the error value.
What happens if you call error() when has_value() is true?
Calling error() in the success state is undefined behavior before C++26, so verify that the result is in the failure state first.
Does value() throw exceptions?
It returns a reference to the value in the success state, but throws std::bad_expected_access in the failure state.
When should you choose std::optional versus C++ std::expected?
Use std::optional when you only need to represent absence; use std::expected when the specific reason for failure must be carried as an error value.
Where to find C++ std::expected documentation?
Short answer: The official cppreference pages for C++23 expected and related utilities document its state model and member preconditions.
- Overview, never-valueless behavior, and the official example: expected overview
- Type for representing an error value:
std::unexpected - Constructors:
expectedconstructors - Checking success:
has_value()andoperator bool - Value access and exceptions:
value()andbad_expected_access - Error access:
error() - Fallback value access:
value_or - Chaining flows:
and_thenandtransform - Wrapper for representing value absence:
optional
'C++' 카테고리의 다른 글
| C++ std::string_view, 문자열을 복사 없이 보는 법 (0) | 2026.09.09 |
|---|---|
| C++ std::span, 배열을 복사 없이 넘기는 법 (0) | 2026.09.07 |
| C++ RAII, 소멸자에서 자원을 묶는 이유 (0) | 2026.09.04 |
| C++ volatile, 임베디드에서 쓰는 이유 (0) | 2026.09.04 |
| C++ unique_ptr와 shared_ptr, 면접에서 고르는 기준 (0) | 2026.09.02 |
