Initial Course
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
add_course_exercise(NAME constexpr_frontier MODULE 14_cpp26 STANDARD 23
|
||||
SOURCES src/constexpr_frontier.cpp test/constexpr_frontier_test.cpp)
|
||||
add_course_exercise(NAME monadic_expected MODULE 14_cpp26 STANDARD 23
|
||||
SOURCES src/monadic_expected.cpp test/monadic_expected_test.cpp)
|
||||
add_course_exercise(NAME pipeline_ranges MODULE 14_cpp26 STANDARD 23
|
||||
SOURCES src/pipeline_ranges.cpp test/pipeline_ranges_test.cpp)
|
||||
add_course_exercise(NAME custom_formatter MODULE 14_cpp26 STANDARD 23
|
||||
SOURCES src/custom_formatter.cpp test/custom_formatter_test.cpp)
|
||||
@@ -0,0 +1,20 @@
|
||||
# Module 14: C++26 and the Modern Frontier
|
||||
|
||||
## Learning Goals
|
||||
|
||||
- Push `constexpr` and `consteval` to compile-time limits
|
||||
- Chain operations on `std::expected` in a monadic style
|
||||
- Build expressive ranges pipelines for real data tasks
|
||||
- Specialize `std::formatter` for domain types (library direction through C++26)
|
||||
|
||||
> **Note:** C++26 is still evolving. These exercises use C++23 as the baseline and
|
||||
> focus on techniques and library directions that remain central in C++26.
|
||||
|
||||
## Exercises
|
||||
|
||||
| Exercise | Topic |
|
||||
|----------|-------|
|
||||
| `constexpr_frontier` | consteval, compile-time algorithms |
|
||||
| `monadic_expected` | Monadic expected composition |
|
||||
| `pipeline_ranges` | Advanced ranges pipelines |
|
||||
| `custom_formatter` | std::formatter specialization |
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
|
||||
namespace cpp26 {
|
||||
|
||||
[[nodiscard]] auto compile_time_factorial(int n) -> long long;
|
||||
[[nodiscard]] auto compile_time_sum(std::array<int, 5> values) -> int;
|
||||
[[nodiscard]] auto is_sorted(std::array<int, 5> values) -> bool;
|
||||
|
||||
} // namespace cpp26
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <format>
|
||||
#include <string>
|
||||
|
||||
namespace cpp26 {
|
||||
|
||||
struct Point {
|
||||
int x;
|
||||
int y;
|
||||
};
|
||||
|
||||
[[nodiscard]] auto format_point(const Point& point) -> std::string;
|
||||
[[nodiscard]] auto format_points(const Point& a, const Point& b) -> std::string;
|
||||
|
||||
} // namespace cpp26
|
||||
|
||||
template <>
|
||||
struct std::formatter<cpp26::Point> : std::formatter<std::string> {
|
||||
auto format(const cpp26::Point& point, std::format_context& ctx) const {
|
||||
return std::formatter<std::string>::format(
|
||||
std::format("({}, {})", point.x, point.y), ctx);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <expected>
|
||||
#include <string>
|
||||
|
||||
namespace cpp26 {
|
||||
|
||||
enum class ErrorCode { NotFound, Invalid };
|
||||
|
||||
[[nodiscard]] auto parse_id(const std::string& text) -> std::expected<int, ErrorCode>;
|
||||
[[nodiscard]] auto load_user(int id) -> std::expected<std::string, ErrorCode>;
|
||||
[[nodiscard]] auto load_user_name(const std::string& text) -> std::expected<std::string, ErrorCode>;
|
||||
|
||||
} // namespace cpp26
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace cpp26 {
|
||||
|
||||
struct Record {
|
||||
std::string category;
|
||||
int value;
|
||||
};
|
||||
|
||||
[[nodiscard]] auto top_values_by_category(const std::vector<Record>& records, int min_value)
|
||||
-> std::vector<Record>;
|
||||
[[nodiscard]] auto total_by_category(const std::vector<Record>& records)
|
||||
-> std::vector<std::pair<std::string, int>>;
|
||||
|
||||
} // namespace cpp26
|
||||
@@ -0,0 +1,20 @@
|
||||
#include "constexpr_frontier.hpp"
|
||||
|
||||
namespace cpp26 {
|
||||
|
||||
auto compile_time_factorial(int n) -> long long {
|
||||
(void)n;
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto compile_time_sum(std::array<int, 5> values) -> int {
|
||||
(void)values;
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto is_sorted(std::array<int, 5> values) -> bool {
|
||||
(void)values;
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace cpp26
|
||||
@@ -0,0 +1,15 @@
|
||||
#include "custom_formatter.hpp"
|
||||
|
||||
namespace cpp26 {
|
||||
|
||||
auto format_point(const Point& point) -> std::string {
|
||||
(void)point;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto format_points(const Point& a, const Point& b) -> std::string {
|
||||
(void)a; (void)b;
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace cpp26
|
||||
@@ -0,0 +1,20 @@
|
||||
#include "monadic_expected.hpp"
|
||||
|
||||
namespace cpp26 {
|
||||
|
||||
auto parse_id(const std::string& text) -> std::expected<int, ErrorCode> {
|
||||
(void)text;
|
||||
return std::unexpected(ErrorCode::Invalid);
|
||||
}
|
||||
|
||||
auto load_user(int id) -> std::expected<std::string, ErrorCode> {
|
||||
(void)id;
|
||||
return std::unexpected(ErrorCode::NotFound);
|
||||
}
|
||||
|
||||
auto load_user_name(const std::string& text) -> std::expected<std::string, ErrorCode> {
|
||||
(void)text;
|
||||
return std::unexpected(ErrorCode::Invalid);
|
||||
}
|
||||
|
||||
} // namespace cpp26
|
||||
@@ -0,0 +1,17 @@
|
||||
#include "pipeline_ranges.hpp"
|
||||
|
||||
namespace cpp26 {
|
||||
|
||||
auto top_values_by_category(const std::vector<Record>& records, int min_value)
|
||||
-> std::vector<Record> {
|
||||
(void)records; (void)min_value;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto total_by_category(const std::vector<Record>& records)
|
||||
-> std::vector<std::pair<std::string, int>> {
|
||||
(void)records;
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace cpp26
|
||||
@@ -0,0 +1,15 @@
|
||||
#include "constexpr_frontier.hpp"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
TEST(ConstexprFrontier, Factorial) {
|
||||
EXPECT_EQ(cpp26::compile_time_factorial(5), 120);
|
||||
}
|
||||
|
||||
TEST(ConstexprFrontier, CompileTimeSum) {
|
||||
EXPECT_EQ(cpp26::compile_time_sum({1, 2, 3, 4, 5}), 15);
|
||||
}
|
||||
|
||||
TEST(ConstexprFrontier, IsSorted) {
|
||||
EXPECT_TRUE(cpp26::is_sorted({1, 2, 3, 4, 5}));
|
||||
EXPECT_FALSE(cpp26::is_sorted({1, 3, 2, 4, 5}));
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#include "custom_formatter.hpp"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
TEST(CustomFormatter, FormatPoint) {
|
||||
EXPECT_EQ(cpp26::format_point({3, 4}), "(3, 4)");
|
||||
}
|
||||
|
||||
TEST(CustomFormatter, FormatPoints) {
|
||||
EXPECT_EQ(cpp26::format_points({1, 2}, {3, 4}), "(1, 2) -> (3, 4)");
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#include "monadic_expected.hpp"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
TEST(MonadicExpected, LoadUserName) {
|
||||
EXPECT_EQ(*cpp26::load_user_name("42"), "User-42");
|
||||
EXPECT_EQ(cpp26::load_user_name("x"), std::unexpected(cpp26::ErrorCode::Invalid));
|
||||
EXPECT_EQ(cpp26::load_user_name("999"), std::unexpected(cpp26::ErrorCode::NotFound));
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#include "pipeline_ranges.hpp"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
TEST(PipelineRanges, TopValuesByCategory) {
|
||||
const std::vector<cpp26::Record> records{
|
||||
{"math", 10}, {"math", 50}, {"code", 30}, {"code", 5}};
|
||||
const auto result = cpp26::top_values_by_category(records, 20);
|
||||
ASSERT_EQ(result.size(), 2U);
|
||||
EXPECT_EQ(result[0].category, "math");
|
||||
EXPECT_EQ(result[0].value, 50);
|
||||
}
|
||||
|
||||
TEST(PipelineRanges, TotalByCategory) {
|
||||
const std::vector<cpp26::Record> records{{"math", 10}, {"math", 5}, {"code", 7}};
|
||||
const auto totals = cpp26::total_by_category(records);
|
||||
ASSERT_EQ(totals.size(), 2U);
|
||||
EXPECT_EQ(totals[0].first, "code");
|
||||
EXPECT_EQ(totals[0].second, 7);
|
||||
}
|
||||
Reference in New Issue
Block a user