1) 한눈에 보는 매핑(요약)
| Kotlin 개념 | C++에서의 대응 | 비고 |
|---|---|---|
val/var (불변/가변) | const / 일반 변수 | 객체 자체의 불변성은 const, 멤버 불변엔 const/mutable/캡슐화 |
Null-safety (T?, ?., ?:) | std::optional<T>, 안전 래퍼, value_or, 유틸 템플릿 | 레퍼런스는 널 불가, 포인터는 널 가능 |
| data class | 평범한 struct + = default 비교 + ostream<< + “copy-with” 헬퍼 | 생성/비교/출력 자동화 느낌 재현 |
sealed class + when | std::variant + std::visit(오버로드 람다) | ADT 모델링 |
| 확장 함수 | 네임스페이스 내부 자유 함수 + ADL | 점표기법은 불가(제약) |
스코프 함수(let/apply/also/run/with) | 범용 템플릿 유틸(let, apply, also) | DSL 느낌 살리기 |
| Sequence(지연 연산) | C++20 Ranges (std::views::…) | 매우 강력하고 타입 안정적 |
| 제너레이터 | C++20 코루틴 co_yield 기반 제너레이터 | 표준 제너레이터 타입은 없어서 경량 구현 예시 제공 |
| 코루틴(suspend) | C++20 코루틴(co_await/co_return) + 커스텀 task | 런타임이 얇아 직접 타입을 설계해야 함 |
| 패턴 매칭 | std::visit(변형) / if constexpr / std::expected | 언어 내 패턴매칭 키워드는 아직 없음 |
2) Null-safety 이식
2.1 Optional과 “엘비스” 연산자 대체
#include <optional>
#include <string>
using std::optional, std::string;
optional<string> find_name(int id);
int main() {
optional<string> s = find_name(42);
// Kotlin: val len = s?.length ?: 0
auto len = s ? s->size() : 0u; // 삼항
auto val = s.value_or("default"); // value_or는 엘비스 대체
}
2.2 안전 변환 유틸 (Kotlin ?.map { … } 느낌)
#include <optional>
#include <utility>
#include <type_traits>
template<class T, class F>
auto opt_map(const std::optional<T>& o, F&& f)
-> std::optional<std::invoke_result_t<F,const T&>> {
if (o) return std::invoke(std::forward<F>(f), *o);
return std::nullopt;
}
3) data class 느낌 살리기
#include <compare>
#include <ostream>
#include <string>
#include <optional>
struct User {
std::string name;
int age{};
// Kotlin data class처럼 비교/동치 자동화 느낌
auto operator<=>(const User&) const = default;
};
// "copy(with: …)" 유틸 (선택적 수정)
inline User copy(User u,
std::optional<std::string> name = std::nullopt,
std::optional<int> age = std::nullopt) {
if (name) u.name = *name;
if (age) u.age = *age;
return u;
}
inline std::ostream& operator<<(std::ostream& os, const User& u) {
return os << "User{name=" << u.name << ", age=" << u.age << "}";
}
사용:
User a{"Neo", 42};
User b = copy(a, /*name=*/std::string("Trinity")); // age 유지
Destructuring: C++17의 구조적 바인딩으로 유사 표현 가능(집계 초기화 가능형/tuple-like일 때).
4) sealed class + when → std::variant + std::visit
#include <variant>
#include <cmath>
struct Circle { double r; };
struct Rect { double w, h; };
using Shape = std::variant<Circle, Rect>;
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
double area(const Shape& s) {
return std::visit(overloaded{
[](const Circle& c){ return M_PI * c.r * c.r; },
[](const Rect& r){ return r.w * r.h; }
}, s);
}
- Kotlin의
sealed + when(exhaustive)가variant + visit로 깔끔히 대응됩니다. - 열거가 닫혀 있음(closed set)도
variant가 자연히 보장.
5) 확장 함수 & 스코프 함수(DLS 풍)
5.1 “확장”은 자유 함수 + ADL로
#include <string>
#include <algorithm>
namespace text {
inline std::string padZ(std::string s, std::size_t n) {
if (s.size() < n) s.insert(s.begin(), n - s.size(), '0');
return s;
}
}
using text::padZ;
auto id = padZ("7", 3); // "007"
진짜 “메서드처럼 점 표기”는 언어적으론 불가. 자유 함수/프렌드/ADL 조합을 씁니다.
5.2 스코프 함수 흉내: let/also/apply
#include <utility>
#include <functional>
template<class T, class F>
auto let(T&& x, F&& f) {
return std::invoke(std::forward<F>(f), std::forward<T>(x));
}
template<class T, class F>
T& also(T& x, F&& f) {
std::invoke(std::forward<F>(f), x);
return x;
}
template<class T, class F>
T& apply(T& x, F&& f) {
std::invoke(std::forward<F>(f), x); // this-수신자 컨셉 흉내
return x;
}
// 사용
#include <vector>
#include <iostream>
int main() {
std::vector<int> v;
apply(v, [](auto& self){ self.push_back(1); self.push_back(2); })
.also([](auto& self){ std::cout << "size=" << self.size() << "\n"; });
auto len = let(std::string("abc"), [](auto&& s){ return s.size(); });
}
6) Sequence & 제너레이터
6.1 C++20 Ranges로 “지연 파이프라인”
#include <ranges>
#include <vector>
#include <iostream>
int main() {
using namespace std::ranges;
std::vector<int> xs = {1,2,3,4,5};
auto view = xs
| views::transform([](int x){ return x*2; })
| views::filter([](int x){ return x>5; })
| views::take(3);
for (int v : view) std::cout << v << " "; // 6 8 10
}
- Kotlin
sequence { … }.map.filter.take의 완전한 대응. - 지연이 기본이라 대용량에도 효율적.
6.2 경량 “제너레이터”(co_yield) 구현 예
표준
std::generator는 아직(표준 라이브러리로) 부재. 교육용으로 최소 구현을 제시합니다.
#include <coroutine>
#include <optional>
#include <utility>
template<class T>
struct generator {
struct promise_type {
std::optional<T> current;
generator get_return_object() { return generator{handle_type::from_promise(*this)}; }
std::suspend_always initial_suspend() noexcept { return {}; }
std::suspend_always yield_value(T v) noexcept { current = std::move(v); return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() { throw; }
void return_void() {}
};
using handle_type = std::coroutine_handle<promise_type>;
handle_type h;
explicit generator(handle_type h) : h(h) {}
generator(generator&& other) noexcept : h(std::exchange(other.h, {})) {}
~generator(){ if (h) h.destroy(); }
struct iter {
handle_type h;
bool done = false;
void operator++(){ h.resume(); done = h.done(); }
const T& operator*() const { return *h.promise().current; }
bool operator!=(std::default_sentinel_t) const { return !done; }
};
iter begin() { if (h) { h.resume(); } return {h, h.done()}; }
std::default_sentinel_t end() { return {}; }
};
// 사용: 무한 카운터
generator<int> counter() {
int i = 0;
while (true) co_yield i++;
}
#include <iostream>
int main() {
int n = 0;
for (auto x : counter()) {
std::cout << x << " ";
if (++n == 5) break; // 0 1 2 3 4
}
}
7) 코루틴(suspend) 스타일 비동기
C++20 코루틴은 언어 메커니즘만 제공하고 런타임/스케줄러는 사용자가(혹은 라이브러리가) 설계합니다. 교육용 단일 스레드 task<T> 예:
#include <coroutine>
#include <exception>
#include <optional>
#include <utility>
template<class T>
struct task {
struct promise_type {
std::optional<T> value;
std::exception_ptr eptr;
task get_return_object() { return task{std::coroutine_handle<promise_type>::from_promise(*this)}; }
std::suspend_always initial_suspend() noexcept { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception(){ eptr = std::current_exception(); }
void return_value(T v){ value = std::move(v); }
};
using handle = std::coroutine_handle<promise_type>;
handle h;
explicit task(handle h):h(h){}
task(task&& o) noexcept : h(std::exchange(o.h, {})){}
~task(){ if (h) h.destroy(); }
// 데모용 블로킹 get (실전은 이벤트 루프/스케줄러 사용)
T get() {
h.resume();
if (h.promise().eptr) std::rethrow_exception(h.promise().eptr);
return std::move(*(h.promise().value));
}
};
task<int> fetch_answer() {
co_return 42; // 비동기 처럼 보이는 동기 예시
}
int main() {
auto t = fetch_answer();
int v = t.get();
// 실제 네트워킹/스레딩은 별도 실행기(executor)가 필요
}
핵심: Kotlin 코루틴은 “구조적 동시성·스케줄러·Scope”를 표준 라이브러리로 제공합니다. C++은 언어 코루틴만 있어, 실제 사용에선 실행기/스케줄러(혹은 외부 라이브러리)를 함께 구성해야 Kotlin과 동일한 UX가 나옵니다.
8) 작은 “by lazy” 구현 (지연 초기화 프로퍼티)
#include <optional>
#include <functional>
template<class F>
class lazy {
mutable std::optional<std::invoke_result_t<F&>> v;
F f;
public:
explicit lazy(F fun) : f(std::move(fun)) {}
auto get() const -> const std::invoke_result_t<F&>& {
if (!v) v = f();
return *v;
}
};
template<class F>
lazy<F> make_lazy(F f){ return lazy<F>(std::move(f)); }
// 사용
#include <string>
auto name = make_lazy([]{ return std::string("heavy-compute"); });
// name.get() 호출 시 최초 1회 계산
9) “Kotlin 느낌”의 테스트 가능 API 구성
- 도메인 모델:
variant/data struct - 오류 전파:
std::expected<T,E>(C++23) 또는std::optional/예외 - 비동기 경계: 코루틴 task + 실행기
- DSL화: 스코프/확장 유틸 + Ranges
예: 간단한 파이프라인
#include <expected>
#include <string>
#include <ranges>
std::expected<std::string, std::string> read_conf();
std::expected<int, std::string> parse_port(const std::string& s);
auto boot() -> std::expected<void, std::string> {
auto conf = read_conf();
if (!conf) return std::unexpected(conf.error());
auto port = parse_port(*conf);
if (!port) return std::unexpected(port.error());
// ranges로 사소한 전처리
using namespace std::ranges;
auto ok = (*conf | views::filter([](char c){ return !std::isspace((unsigned char)c); })).size() > 0;
if (!ok) return std::unexpected("empty config");
return {};
}
10) 팀 적용 체크리스트
- 규약: 기본은
const우선, 포인터 대신 레퍼런스/not_null사용 - 모델링:
std::variant+std::visit로 폐쇄형 도메인 모델 - 에러:
std::expected또는 예외 한 가지로 통일 - 지연/파이프라인: Ranges 적극 사용(성능+가독성)
- 코루틴: 최소 제너레이터부터 도입 → 필요 시 실행기 추가
- 도우미:
let/also/apply/opt_map/copy-with같은 미니 유틸 공용화
11) 요약 코드(한 파일에 모아 쓰는 스타터 킷)
// kotlin_port.hpp — “Kotlin 감성” 유틸 모음 (헤더 하나로 시작)
#pragma once
#include <optional>
#include <utility>
#include <type_traits>
#include <variant>
#include <coroutine>
#include <expected>
// ---------- Optional helpers ----------
template<class T, class F>
auto opt_map(const std::optional<T>& o, F&& f)
-> std::optional<std::invoke_result_t<F,const T&>> {
if (o) return std::invoke(std::forward<F>(f), *o);
return std::nullopt;
}
// ---------- Scope-like helpers ----------
template<class T, class F>
auto let(T&& x, F&& f) { return std::invoke(std::forward<F>(f), std::forward<T>(x)); }
template<class T, class F>
T& also(T& x, F&& f) { std::invoke(std::forward<F>(f), x); return x; }
template<class T, class F>
T& apply(T& x, F&& f) { std::invoke(std::forward<F>(f), x); return x; }
// ---------- Variant visit helper ----------
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
// ---------- Minimal generator (co_yield) ----------
template<class T>
struct generator {
struct promise_type {
std::optional<T> current;
generator get_return_object() { return generator{handle_type::from_promise(*this)}; }
std::suspend_always initial_suspend() noexcept { return {}; }
std::suspend_always yield_value(T v) noexcept { current = std::move(v); return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() { throw; }
void return_void() {}
};
using handle_type = std::coroutine_handle<promise_type>;
handle_type h;
explicit generator(handle_type h) : h(h) {}
generator(generator&& o) noexcept : h(std::exchange(o.h, {})) {}
~generator(){ if (h) h.destroy(); }
struct iter {
handle_type h; bool done=false;
void operator++(){ h.resume(); done=h.done(); }
const T& operator*() const { return *h.promise().current; }
bool operator!=(std::default_sentinel_t) const { return !done; }
};
iter begin(){ if(h) h.resume(); return {h,h.done()}; }
std::default_sentinel_t end(){ return {}; }
};
// ---------- Minimal task (co_return) — 교육용 ----------
template<class T>
struct task {
struct promise_type {
std::optional<T> value; std::exception_ptr eptr;
task get_return_object(){ return task{std::coroutine_handle<promise_type>::from_promise(*this)}; }
std::suspend_always initial_suspend() noexcept { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception(){ eptr = std::current_exception(); }
void return_value(T v){ value = std::move(v); }
};
using handle = std::coroutine_handle<promise_type>;
handle h; explicit task(handle h):h(h){} task(task&& o) noexcept: h(std::exchange(o.h,{})){} ~task(){ if(h) h.destroy(); }
T get(){ h.resume(); if(h.promise().eptr) std::rethrow_exception(h.promise().eptr); return std::move(*h.promise().value); }
};
12) C++로 구현이 어려운/불가능(혹은 불편한) 것들
- 언어 차원의 Null-safety 강제: C++는 포인터 널을 언어가 금지하지 않습니다. 가이드/래퍼(
not_null,optional)로 보완합니다. - 확장 “메서드” 문법: Kotlin처럼 점표기법으로 기존 타입에 메서드 추가는 불가. 자유 함수 + ADL/프렌드로 대체합니다.
- sealed class 키워드: “번들 밖에서 서브타입 금지”를 언어가 보장하진 않습니다. 대신
std::variant로 닫힌 합 타입을 모델링하세요. - 언어 내 패턴 매칭:
when수준의 문법은 없습니다.std::visit/if constexpr 패턴으로 대체합니다. - 코루틴 런타임/구조적 동시성: Kotlin은 라이브러리가 제공하지만 C++은 프레임워크 부재가 기본값입니다. 실행기/스케줄링/취소 토큰 등은 직접(또는 외부 라이브러리로) 설계해야 합니다.
- 런타임 리플렉션 & 애노테이션 메타데이터: Kotlin만큼 풍부하지 않습니다(템플릿/메타프로그래밍으로 대체, 표준 반사는 미표준).
맺음말
Kotlin의 생산성 포인트는 타입으로 안전을 강제하고 표현을 간결화하는 데 있습니다. C++에서도 Ranges/Variant/Optional/Coroutines를 적절히 조합하면 거의 동일한 모델링과 사용성을 얻을 수 있습니다. 위 “스타터 킷”을 팀 공용 헤더로 두고, 도메인 모델은 variant+visit, 비동기는 “필요할 때만” 제너레이터/코루틴을 도입해 점진적으로 확장해 보세요.