C++ 문법 선택 기준표: tuple, 구조적 바인딩, using, <=>, 사용자 정의 리터럴을 직접 실행해 고르기
이 글의 핵심
같은 일을 하는 문법이 여러 개일 때 어느 쪽을 쓸지 정하는 기준을 말로만 설명하지 않고, 한 개의 C++ 프로그램으로 실행해 결과를 확인했습니다. 구조적 바인딩의 복사와 참조 차이, using과 typedef가 같은 타입이라는 점, size_t 뺄셈 함정 등을 실행 출력으로 보여 주고 각 주제의 자세한 글을 연결합니다.
C++에는 같은 일을 하는 문법이 여러 개 있고, 코드 리뷰에서 “왜 이걸 썼나요”라는 질문이 나오기 쉽다. 이 글은 그런 선택 열한 가지를 한 개의 프로그램으로 실행해서 결과를 확인하고, 각 주제의 자세한 글로 연결한다. 환경은 Apple clang 21.0.0(libc++), macOS arm64, -std=c++20이다. 다른 컴파일러에서는 크기나 일부 동작이 다를 수 있다.
실행해서 확인한 결과
| 선택 | 실행 결과 | 해석 |
|---|---|---|
tuple<int,double>과 struct{int a; double b;}의 sizeof | 둘 다 16 | 크기는 같으므로 이름 유무로 고른다 |
for (auto [k, v] : m)에서 v += 10 | m["a"]는 그대로 1 | 복사본을 분해해서 원본이 안 바뀜 |
for (auto& [k, v] : m)에서 v += 10 | m["a"]가 11 | 참조라서 원본이 바뀜 |
using IntFn = int(*)(int)과 typedef int(*IntFn2)(int) | is_same_v가 참(1) | 둘 다 같은 타입의 별칭이다 |
using Id = int; | is_same_v<Id, int>가 참 | 별칭은 새 타입이 아니다 |
struct Pt{...; auto operator<=>(const Pt&) const = default;} | a<b 참, a==b 거짓, a>=b 거짓 | 비교 연산자 여섯 개를 기본 구현으로 얻음 |
사용자 정의 리터럴 2.5_km | 2500 | 접미사로 단위를 코드에 드러냄 |
[p = std::move(up)] 초기화 캡처 | 호출 결과 42, 원본 up은 null | 이동으로 람다 안에 소유권을 넘김 |
std::apply(add, std::make_tuple(3, 4)) | 7 | 튜플을 인자로 펼쳐 호출 |
v.size() - 4 (v는 원소 3개) | 18446744073709551615 | 부호 없는 뺄셈이 언더플로 |
std::ssize(v) - 4 | -1 | 부호 있는 크기로 안전하게 |
std::cmp_less(-1, 1u) 와 -1 < 1u | 1(참) 대 0(거짓) | 일반 비교는 부호를 맞추다가 틀린 답 |
static_cast<int>(3.9) | 3 | 소수부를 버림 |
dynamic_cast<D*>(B 객체) | nullptr | 실제 타입이 D가 아니면 실패 |
주제별로 어디를 보면 되나
값 묶기와 분해
여러 값을 한 번에 돌려줄 때는 std::tuple 가이드가, 이를 풀어 쓰는 문법은 구조적 바인딩이 다룬다. 위 표에서 복사와 참조 차이가 바로 구조적 바인딩의 가장 흔한 실수다. 원본을 고칠 의도가 없으면 const auto&, 고칠 의도가 있으면 auto&를 쓴다.
타입 이름과 비교
typedef와 using이 같은 타입을 만든다는 점은 위 실행으로 확인했다. 템플릿 별칭이 필요하면 using만 가능하다는 점까지 포함해 typedef vs using 글에 있다. 비교 연산자를 직접 여섯 개 쓰는 대신 <=>를 = default로 두는 방법은 삼원 비교 연산자에서 순서 종류(strong, weak, partial)까지 설명한다. 단위를 타입처럼 드러내고 싶으면 사용자 정의 리터럴을 쓴다.
람다와 호출
이동 전용 객체를 람다로 넘기는 방법은 초기화 캡처에 있다. 튜플을 인자로 펼치는 std::apply와 호출 규칙을 일반화한 std::invoke는 invoke와 apply에서 다룬다. 고차 함수와 Ranges로 넘어가는 이야기는 함수형 프로그래밍 심화가 이어 받는다.
숫자와 형 변환
size_t로 인덱스를 쓰다가 뺄셈에서 거대한 수가 나오는 함정은 위 표에서 그대로 재현됐고, 해결책은 size_t와 ptrdiff_t 글에 정리돼 있다. 형 변환 네 가지의 선택 기준은 C++ 캐스팅 가이드에, 반복문과 off-by-one 실수는 반복문 정리에 있다.
선택 기준 한 줄 요약
- 이름이 의미를 줄 때는 struct, 잠깐 묶을 때만 tuple.
- 구조적 바인딩은 기본이 복사라는 점을 기억하고, 고칠 때만
auto&. - 별칭은
using, 새 타입이 필요하면struct나 enum을 쓴다. - 비교는
<=>기본 구현을 먼저 시도한다. - 인덱스가 음수가 될 수 있으면
std::ssize나std::cmp_*를 쓴다.
이 글에서 확인하지 못한 것
- 다른 컴파일러(GCC, MSVC)와 다른 표준 라이브러리에서의
sizeof값. - 성능 차이. 위 항목은 의미(결과)를 확인했고 속도를 비교하지는 않았다.