C++20 ์‹ฌํ™”

C++20 ์‹ฌํ™”

๊ฐœ์š”

C++20์€ C++11 ์ดํ›„ ๊ฐ€์žฅ ํฐ ๋ณ€ํ™”๋ฅผ ๊ฐ€์ ธ์˜จ ํ‘œ์ค€์ž…๋‹ˆ๋‹ค. Concepts, Ranges, Coroutines, Modules ๋“ฑ ํ˜์‹ ์ ์ธ ๊ธฐ๋Šฅ๋“ค์ด ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ด ์žฅ์—์„œ๋Š” C++20์˜ ํ•ต์‹ฌ ๊ธฐ๋Šฅ๋“ค์„ ํ•™์Šตํ•ฉ๋‹ˆ๋‹ค.

๋‚œ์ด๋„: โญโญโญโญโญ

์„ ์ˆ˜ ์ง€์‹: ํ…œํ”Œ๋ฆฟ, ๋žŒ๋‹ค, ์Šค๋งˆํŠธ ํฌ์ธํ„ฐ


๋ชฉ์ฐจ

  1. Concepts
  2. Ranges
  3. Coroutines
  4. Modules
  5. ๊ธฐํƒ€ C++20 ๊ธฐ๋Šฅ
  6. C++23 ๋ฏธ๋ฆฌ๋ณด๊ธฐ

Concepts

Concepts๋ž€?

ํ…œํ”Œ๋ฆฟ ๋งค๊ฐœ๋ณ€์ˆ˜์— ๋Œ€ํ•œ ์ œ์•ฝ ์กฐ๊ฑด์„ ์ •์˜ํ•˜๋Š” ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. ์ด์ „์˜ SFINAE๋ณด๋‹ค ํ›จ์”ฌ ๊ฐ€๋…์„ฑ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•

#include <concepts>
#include <iostream>

// concept ์ •์˜
template<typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;

// concept ์‚ฌ์šฉ
template<Numeric T>
T add(T a, T b) {
    return a + b;
}

// ๋˜๋Š” requires ์ ˆ ์‚ฌ์šฉ
template<typename T>
    requires Numeric<T>
T multiply(T a, T b) {
    return a * b;
}

// ๋˜๋Š” ํ›„ํ–‰ requires
template<typename T>
T divide(T a, T b) requires Numeric<T> {
    return a / b;
}

int main() {
    std::cout << add(1, 2) << "\n";        // OK
    std::cout << add(1.5, 2.5) << "\n";    // OK
    // add("hello", "world");              // ์ปดํŒŒ์ผ ์—๋Ÿฌ!
    return 0;
}

ํ‘œ์ค€ Concepts

#include <concepts>

// ํƒ€์ž… ๊ด€๋ จ
std::same_as<T, U>           // T์™€ U๊ฐ€ ๊ฐ™์€ ํƒ€์ž…
std::derived_from<D, B>      // D๊ฐ€ B์˜ ํŒŒ์ƒ ํด๋ž˜์Šค
std::convertible_to<From, To>// From์ด To๋กœ ๋ณ€ํ™˜ ๊ฐ€๋Šฅ

// ์‚ฐ์ˆ  ๊ด€๋ จ
std::integral<T>             // ์ •์ˆ˜ ํƒ€์ž…
std::floating_point<T>       // ๋ถ€๋™์†Œ์ˆ˜์  ํƒ€์ž…
std::signed_integral<T>      // ๋ถ€ํ˜ธ ์žˆ๋Š” ์ •์ˆ˜
std::unsigned_integral<T>    // ๋ถ€ํ˜ธ ์—†๋Š” ์ •์ˆ˜

// ๋น„๊ต ๊ด€๋ จ
std::equality_comparable<T>  // == ์—ฐ์‚ฐ ๊ฐ€๋Šฅ
std::totally_ordered<T>      // <, >, <=, >= ์—ฐ์‚ฐ ๊ฐ€๋Šฅ

// ํ˜ธ์ถœ ๊ด€๋ จ
std::invocable<F, Args...>   // F(Args...)๊ฐ€ ํ˜ธ์ถœ ๊ฐ€๋Šฅ
std::predicate<F, Args...>   // F(Args...)๊ฐ€ bool ๋ฐ˜ํ™˜

์ปค์Šคํ…€ Concept ์ •์˜

#include <concepts>
#include <string>

// ๋ฌธ์ž์—ด์ฒ˜๋Ÿผ ๋™์ž‘ํ•˜๋Š” ํƒ€์ž…
template<typename T>
concept StringLike = requires(T t) {
    { t.length() } -> std::convertible_to<std::size_t>;
    { t.c_str() } -> std::same_as<const char*>;
    { t[0] } -> std::convertible_to<char>;
};

// ์ปจํ…Œ์ด๋„ˆ concept
template<typename T>
concept Container = requires(T t) {
    typename T::value_type;
    typename T::iterator;
    { t.begin() } -> std::same_as<typename T::iterator>;
    { t.end() } -> std::same_as<typename T::iterator>;
    { t.size() } -> std::convertible_to<std::size_t>;
};

// ์‚ฌ์šฉ
template<Container C>
void printContainer(const C& container) {
    for (const auto& item : container) {
        std::cout << item << " ";
    }
    std::cout << "\n";
}

requires ํ‘œํ˜„์‹

// ๋‹จ์ˆœ ์š”๊ตฌ์‚ฌํ•ญ
template<typename T>
concept Addable = requires(T a, T b) {
    a + b;  // ์ด ํ‘œํ˜„์‹์ด ์œ ํšจํ•ด์•ผ ํ•จ
};

// ํƒ€์ž… ์š”๊ตฌ์‚ฌํ•ญ
template<typename T>
concept HasValueType = requires {
    typename T::value_type;
};

// ๋ณตํ•ฉ ์š”๊ตฌ์‚ฌํ•ญ
template<typename T>
concept Hashable = requires(T t) {
    { std::hash<T>{}(t) } -> std::convertible_to<std::size_t>;
};

// ์ค‘์ฒฉ ์š”๊ตฌ์‚ฌํ•ญ
template<typename T>
concept Sortable = requires(T t) {
    requires std::totally_ordered<typename T::value_type>;
    { t.begin() } -> std::random_access_iterator;
};

Concept์„ ์ด์šฉํ•œ ์˜ค๋ฒ„๋กœ๋”ฉ

#include <concepts>
#include <iostream>

template<std::integral T>
void print(T value) {
    std::cout << "Integer: " << value << "\n";
}

template<std::floating_point T>
void print(T value) {
    std::cout << "Float: " << value << "\n";
}

template<typename T>
void print(T value) {
    std::cout << "Other: " << value << "\n";
}

int main() {
    print(42);       // Integer: 42
    print(3.14);     // Float: 3.14
    print("hello");  // Other: hello
    return 0;
}

Ranges

Ranges๋ž€?

์ปจํ…Œ์ด๋„ˆ์™€ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ๋” ์šฐ์•„ํ•˜๊ฒŒ ๋‹ค๋ฃจ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค. ํŒŒ์ดํ”„๋ผ์ธ ์Šคํƒ€์ผ์˜ ์—ฐ์‚ฐ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•

#include <ranges>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    // ๊ธฐ์กด ๋ฐฉ์‹
    // for (auto it = nums.begin(); it != nums.end(); ++it) { ... }

    // Ranges ๋ฐฉ์‹
    for (int n : nums | std::views::filter([](int x) { return x % 2 == 0; })
                      | std::views::transform([](int x) { return x * x; })) {
        std::cout << n << " ";  // 4 16 36 64 100
    }

    return 0;
}

Views (๋ทฐ)

๋ทฐ๋Š” ์ง€์—ฐ ํ‰๊ฐ€๋˜๋ฉฐ, ์›๋ณธ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณต์‚ฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

#include <ranges>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    // filter: ์กฐ๊ฑด์— ๋งž๋Š” ์š”์†Œ๋งŒ
    auto evens = v | std::views::filter([](int x) { return x % 2 == 0; });

    // transform: ๋ณ€ํ™˜
    auto squared = v | std::views::transform([](int x) { return x * x; });

    // take: ์ฒ˜์Œ n๊ฐœ
    auto first3 = v | std::views::take(3);

    // drop: ์ฒ˜์Œ n๊ฐœ ์ œ์™ธ
    auto afterFirst3 = v | std::views::drop(3);

    // reverse: ์—ญ์ˆœ
    auto reversed = v | std::views::reverse;

    // ์กฐํ•ฉ
    auto result = v | std::views::filter([](int x) { return x > 3; })
                    | std::views::transform([](int x) { return x * 2; })
                    | std::views::take(3);

    for (int n : result) {
        std::cout << n << " ";  // 8 10 12
    }

    return 0;
}

์ฃผ์š” Views

#include <ranges>
namespace views = std::views;

// ์ƒ์„ฑ ๋ทฐ
auto r1 = views::iota(1, 10);        // 1, 2, ..., 9
auto r2 = views::iota(1) | views::take(10);  // ๋ฌดํ•œ ์‹œํ€€์Šค์—์„œ 10๊ฐœ

// ๋ณ€ํ™˜ ๋ทฐ
auto r3 = v | views::transform(func);
auto r4 = v | views::filter(pred);
auto r5 = v | views::take(n);
auto r6 = v | views::drop(n);
auto r7 = v | views::take_while(pred);
auto r8 = v | views::drop_while(pred);
auto r9 = v | views::reverse;

// ๋ถ„ํ•  ๋ทฐ
auto r10 = str | views::split(' ');  // ๊ณต๋ฐฑ์œผ๋กœ ๋ถ„ํ• 

// ์ ‘ํ•ฉ ๋ทฐ
auto r11 = nested | views::join;     // ์ค‘์ฒฉ range ํ‰ํƒ„ํ™”

// ์š”์†Œ ๋ทฐ
auto r12 = pairs | views::elements<0>;  // ํŠœํ”Œ์˜ ์ฒซ ๋ฒˆ์งธ ์š”์†Œ
auto r13 = pairs | views::keys;         // map์˜ ํ‚ค
auto r14 = pairs | views::values;       // map์˜ ๊ฐ’

Range ์•Œ๊ณ ๋ฆฌ์ฆ˜

#include <ranges>
#include <algorithm>
#include <vector>

int main() {
    std::vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6};

    // ๋ฒ”์œ„ ๊ธฐ๋ฐ˜ ์•Œ๊ณ ๋ฆฌ์ฆ˜
    std::ranges::sort(v);
    std::ranges::reverse(v);

    auto it = std::ranges::find(v, 5);
    bool found = std::ranges::contains(v, 5);

    int count = std::ranges::count_if(v, [](int x) { return x > 3; });

    auto [min, max] = std::ranges::minmax(v);

    // ํ”„๋กœ์ ์…˜
    struct Person {
        std::string name;
        int age;
    };

    std::vector<Person> people = {{"Alice", 30}, {"Bob", 25}};
    std::ranges::sort(people, {}, &Person::age);  // age๋กœ ์ •๋ ฌ

    return 0;
}

Coroutines

Coroutines๋ž€?

์‹คํ–‰์„ ์ผ์‹œ ์ค‘๋‹จํ•˜๊ณ  ๋‚˜์ค‘์— ์žฌ๊ฐœํ•  ์ˆ˜ ์žˆ๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.

๊ธฐ๋ณธ ๊ตฌ์กฐ

#include <coroutine>
#include <iostream>

// ์ฝ”๋ฃจํ‹ด ๋ฐ˜ํ™˜ ํƒ€์ž…
template<typename T>
struct Generator {
    struct promise_type {
        T current_value;

        Generator get_return_object() {
            return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};
        }

        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void unhandled_exception() { std::terminate(); }

        std::suspend_always yield_value(T value) {
            current_value = value;
            return {};
        }

        void return_void() {}
    };

    std::coroutine_handle<promise_type> handle;

    explicit Generator(std::coroutine_handle<promise_type> h) : handle(h) {}
    ~Generator() { if (handle) handle.destroy(); }

    Generator(Generator&& other) noexcept : handle(other.handle) {
        other.handle = nullptr;
    }

    bool next() {
        if (!handle.done()) {
            handle.resume();
        }
        return !handle.done();
    }

    T value() const {
        return handle.promise().current_value;
    }
};

// ์ฝ”๋ฃจํ‹ด ํ•จ์ˆ˜
Generator<int> range(int start, int end) {
    for (int i = start; i < end; ++i) {
        co_yield i;  // ๊ฐ’์„ yieldํ•˜๊ณ  ์ผ์‹œ ์ค‘๋‹จ
    }
}

int main() {
    auto gen = range(1, 5);

    while (gen.next()) {
        std::cout << gen.value() << " ";  // 1 2 3 4
    }

    return 0;
}

co_await, co_yield, co_return

// co_yield: ๊ฐ’์„ ์‚ฐ์ถœํ•˜๊ณ  ์ผ์‹œ ์ค‘๋‹จ
Generator<int> numbers() {
    co_yield 1;
    co_yield 2;
    co_yield 3;
}

// co_return: ์ฝ”๋ฃจํ‹ด ์ข…๋ฃŒ
Task<int> compute() {
    // ๋น„๋™๊ธฐ ์ž‘์—…...
    co_return 42;
}

// co_await: awaitable ๊ฐ์ฒด ๋Œ€๊ธฐ
Task<void> asyncWork() {
    auto result = co_await asyncOperation();
    // result ์‚ฌ์šฉ...
}

์‹ค์šฉ์ ์ธ ์˜ˆ: ๊ฐ„๋‹จํ•œ Task

#include <coroutine>
#include <optional>
#include <iostream>

template<typename T>
struct Task {
    struct promise_type {
        std::optional<T> result;

        Task get_return_object() {
            return Task{std::coroutine_handle<promise_type>::from_promise(*this)};
        }

        std::suspend_never initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void unhandled_exception() { std::terminate(); }

        void return_value(T value) {
            result = value;
        }
    };

    std::coroutine_handle<promise_type> handle;

    Task(std::coroutine_handle<promise_type> h) : handle(h) {}
    ~Task() { if (handle) handle.destroy(); }

    T get() {
        return *handle.promise().result;
    }
};

Task<int> asyncAdd(int a, int b) {
    co_return a + b;
}

int main() {
    auto task = asyncAdd(10, 20);
    std::cout << "Result: " << task.get() << "\n";
    return 0;
}

Modules

Modules๋ž€?

ํ—ค๋” ํŒŒ์ผ์˜ ๋‹จ์ ์„ ํ•ด๊ฒฐํ•˜๋Š” ์ƒˆ๋กœ์šด ์ฝ”๋“œ ๊ตฌ์„ฑ ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.

๋ชจ๋“ˆ ์ •์˜

// math.cppm (๋ชจ๋“ˆ ์ธํ„ฐํŽ˜์ด์Šค)
export module math;

export int add(int a, int b) {
    return a + b;
}

export int multiply(int a, int b) {
    return a * b;
}

// ๋‚ด๋ถ€ ๊ตฌํ˜„ (export ์•ˆ ํ•จ)
int helper() {
    return 42;
}

๋ชจ๋“ˆ ์‚ฌ์šฉ

// main.cpp
import math;
import <iostream>;

int main() {
    std::cout << add(1, 2) << "\n";
    std::cout << multiply(3, 4) << "\n";
    return 0;
}

์ปดํŒŒ์ผ (GCC ์˜ˆ)

# ๋ชจ๋“ˆ ์ปดํŒŒ์ผ
g++ -std=c++20 -fmodules-ts -c math.cppm

# ๋ฉ”์ธ ์ปดํŒŒ์ผ ๋ฐ ๋งํฌ
g++ -std=c++20 -fmodules-ts main.cpp math.o -o main

๋ชจ๋“ˆ ์žฅ์ 

๊ธฐ์กด ํ—ค๋” ๋ชจ๋“ˆ
๋งค๋ฒˆ ํŒŒ์‹ฑ ํ•œ ๋ฒˆ๋งŒ ์ปดํŒŒ์ผ
๋งคํฌ๋กœ ์˜ค์—ผ ๊ฒฉ๋ฆฌ๋จ
ํฌํ•จ ์ˆœ์„œ ์ค‘์š” ์ˆœ์„œ ๋ฌด๊ด€
๋А๋ฆฐ ๋นŒ๋“œ ๋น ๋ฅธ ๋นŒ๋“œ

๊ธฐํƒ€ C++20 ๊ธฐ๋Šฅ

์‚ผํ•ญ ๋น„๊ต ์—ฐ์‚ฐ์ž (Spaceship Operator)

#include <compare>

struct Point {
    int x, y;

    auto operator<=>(const Point&) const = default;
    // ==, !=, <, >, <=, >= ์ž๋™ ์ƒ์„ฑ
};

int main() {
    Point p1{1, 2}, p2{1, 3};

    if (p1 < p2) { /* ... */ }
    if (p1 == p2) { /* ... */ }

    auto result = p1 <=> p2;
    if (result < 0) { /* p1 < p2 */ }

    return 0;
}

์ง€์ • ์ดˆ๊ธฐํ™”์ž

struct Config {
    int width = 800;
    int height = 600;
    bool fullscreen = false;
    const char* title = "App";
};

int main() {
    // C++20 ์ง€์ • ์ดˆ๊ธฐํ™”
    Config cfg{
        .width = 1920,
        .height = 1080,
        .fullscreen = true
        // title์€ ๊ธฐ๋ณธ๊ฐ’ ์‚ฌ์šฉ
    };

    return 0;
}

consteval๊ณผ constinit

// consteval: ๋ฐ˜๋“œ์‹œ ์ปดํŒŒ์ผ ํƒ€์ž„์— ํ‰๊ฐ€
consteval int square(int n) {
    return n * n;
}

constexpr int a = square(5);  // OK
// int b = square(x);         // ์—๋Ÿฌ! x๊ฐ€ ์ƒ์ˆ˜๊ฐ€ ์•„๋‹˜

// constinit: ์ •์  ์ดˆ๊ธฐํ™” ๊ฐ•์ œ
constinit int global = 42;
// constinit int bad = foo();  // ์—๋Ÿฌ! foo()๊ฐ€ constexpr ์•„๋‹˜

std::span

#include <span>
#include <vector>
#include <array>

void process(std::span<int> data) {
    for (int& n : data) {
        n *= 2;
    }
}

int main() {
    int arr[] = {1, 2, 3, 4, 5};
    std::vector<int> vec = {1, 2, 3, 4, 5};
    std::array<int, 5> stdArr = {1, 2, 3, 4, 5};

    process(arr);      // OK
    process(vec);      // OK
    process(stdArr);   // OK

    return 0;
}

std::format

#include <format>
#include <iostream>

int main() {
    std::string s = std::format("Hello, {}!", "World");
    std::cout << s << "\n";

    std::cout << std::format("{:>10}", 42) << "\n";      // ์˜ค๋ฅธ์ชฝ ์ •๋ ฌ
    std::cout << std::format("{:08x}", 255) << "\n";     // 16์ง„์ˆ˜, 0 ์ฑ„์›€
    std::cout << std::format("{:.2f}", 3.14159) << "\n"; // ์†Œ์ˆ˜์  2์ž๋ฆฌ

    return 0;
}

std::source_location

#include <source_location>
#include <iostream>

void log(const std::string& msg,
         const std::source_location& loc = std::source_location::current()) {
    std::cout << loc.file_name() << ":"
              << loc.line() << " "
              << loc.function_name() << ": "
              << msg << "\n";
}

int main() {
    log("Hello!");  // main.cpp:15 main: Hello!
    return 0;
}

C++23 ๋ฏธ๋ฆฌ๋ณด๊ธฐ

std::expected

#include <expected>
#include <string>

std::expected<int, std::string> divide(int a, int b) {
    if (b == 0) {
        return std::unexpected("Division by zero");
    }
    return a / b;
}

int main() {
    auto result = divide(10, 2);
    if (result) {
        std::cout << "Result: " << *result << "\n";
    } else {
        std::cout << "Error: " << result.error() << "\n";
    }
    return 0;
}

std::print

#include <print>

int main() {
    std::print("Hello, {}!\n", "World");
    std::println("Value: {}", 42);  // ์ž๋™ ์ค„๋ฐ”๊ฟˆ
    return 0;
}

std::generator (C++23)

#include <generator>

std::generator<int> range(int start, int end) {
    for (int i = start; i < end; ++i) {
        co_yield i;
    }
}

int main() {
    for (int n : range(1, 10)) {
        std::cout << n << " ";
    }
    return 0;
}

์—ฐ์Šต ๋ฌธ์ œ

๋ฌธ์ œ 1: Concept ์ •์˜

"์ถœ๋ ฅ ๊ฐ€๋Šฅํ•œ" ํƒ€์ž…์„ ๋‚˜ํƒ€๋‚ด๋Š” Concept์„ ์ •์˜ํ•˜์„ธ์š” (operator<< ์ง€์›).

์ •๋‹ต ๋ณด๊ธฐ
template<typename T>
concept Printable = requires(std::ostream& os, T t) {
    { os << t } -> std::same_as<std::ostream&>;
};

template<Printable T>
void print(const T& value) {
    std::cout << value << "\n";
}

๋ฌธ์ œ 2: Range ํŒŒ์ดํ”„๋ผ์ธ

1๋ถ€ํ„ฐ 100๊นŒ์ง€ ์ˆซ์ž ์ค‘ 3์˜ ๋ฐฐ์ˆ˜์ด๋ฉด์„œ 5์˜ ๋ฐฐ์ˆ˜๊ฐ€ ์•„๋‹Œ ์ˆ˜์˜ ์ œ๊ณฑ ํ•ฉ์„ ๊ตฌํ•˜์„ธ์š”.

์ •๋‹ต ๋ณด๊ธฐ
#include <ranges>
#include <numeric>
#include <iostream>

int main() {
    auto result = std::views::iota(1, 101)
        | std::views::filter([](int x) { return x % 3 == 0 && x % 5 != 0; })
        | std::views::transform([](int x) { return x * x; });

    int sum = std::accumulate(result.begin(), result.end(), 0);
    std::cout << "Sum: " << sum << "\n";

    return 0;
}

๋‹ค์Œ ๋‹จ๊ณ„


์ฐธ๊ณ  ์ž๋ฃŒ

to navigate between lessons