[카테고리:] 미분류

  • 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, 비동기는 “필요할 때만” 제너레이터/코루틴을 도입해 점진적으로 확장해 보세요.