Kotlin → C++ 개념 이식


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 + whenstd::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) 팀 적용 체크리스트

  1. 규약: 기본은 const 우선, 포인터 대신 레퍼런스/not_null 사용
  2. 모델링: std::variant + std::visit로 폐쇄형 도메인 모델
  3. 에러: std::expected 또는 예외 한 가지로 통일
  4. 지연/파이프라인: Ranges 적극 사용(성능+가독성)
  5. 코루틴: 최소 제너레이터부터 도입 → 필요 시 실행기 추가
  6. 도우미: 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, 비동기는 “필요할 때만” 제너레이터/코루틴을 도입해 점진적으로 확장해 보세요.

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다