Skip to content

Commit 6c176df

Browse files
feat: initial two notes of cppcon 2025 lectures (#17)
* feat: initial two notes of cppcon 2025 lectures * fix ci check
1 parent 773d71b commit 6c176df

81 files changed

Lines changed: 10527 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#include <iostream>
2+
3+
int main() {
4+
int big = 30000;
5+
short small = big;
6+
short overflow = 40000;
7+
double pi = 3.14159;
8+
int int_pi = pi;
9+
std::cout << "overflow = " << overflow << "\n";
10+
std::cout << "int_pi = " << int_pi << "\n";
11+
return 0;
12+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#include <concepts>
2+
#include <type_traits>
3+
#include <string>
4+
5+
template<typename T>
6+
concept number = std::integral<T> || std::floating_point<T>;
7+
8+
static_assert(number<int>, "int should be number");
9+
static_assert(number<double>, "double should be number");
10+
static_assert(number<char>, "char is integral, so number");
11+
static_assert(!number<std::string>, "string is not number");
12+
13+
int main() { return 0; }
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#include <concepts>
2+
#include <type_traits>
3+
#include <limits>
4+
5+
template<typename T>
6+
concept number = std::integral<T> || std::floating_point<T>;
7+
8+
template<typename T, typename U>
9+
concept smaller_range =
10+
number<T> && number<U> &&
11+
(std::numeric_limits<T>::max() < std::numeric_limits<U>::max() ||
12+
std::numeric_limits<T>::min() > std::numeric_limits<U>::min());
13+
14+
template<typename T, typename U>
15+
concept narrowing_assign =
16+
number<T> && number<U> &&
17+
(
18+
smaller_range<T, U> ||
19+
(std::floating_point<U> && std::integral<T>) ||
20+
(std::integral<T> && std::integral<U> &&
21+
std::signed_integral<U> != std::signed_integral<T>)
22+
);
23+
24+
static_assert(narrowing_assign<short, int>, "int->short is narrowing");
25+
static_assert(narrowing_assign<int, double>, "double->int is narrowing");
26+
static_assert(narrowing_assign<unsigned int, int>, "int->unsigned may narrow");
27+
static_assert(!narrowing_assign<int, short>, "short->int is not narrowing");
28+
static_assert(!narrowing_assign<double, float>, "float->double is not narrowing");
29+
static_assert(!narrowing_assign<int, int>, "int->int is not narrowing");
30+
31+
int main() { return 0; }
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#include <iostream>
2+
#include <type_traits>
3+
#include <limits>
4+
#include <stdexcept>
5+
6+
template<typename T, typename U>
7+
constexpr bool would_narrow(U u) noexcept {
8+
if constexpr (std::is_same_v<T, U>) {
9+
return false;
10+
} else if constexpr (std::is_floating_point_v<U> && std::is_integral_v<T>) {
11+
// floating to integral, check at runtime
12+
} else if constexpr (std::is_floating_point_v<T> && std::is_floating_point_v<U>) {
13+
if constexpr (std::numeric_limits<T>::digits >= std::numeric_limits<U>::digits &&
14+
std::numeric_limits<T>::max() >= std::numeric_limits<U>::max() &&
15+
std::numeric_limits<T>::lowest() <= std::numeric_limits<U>::lowest()) {
16+
return false;
17+
}
18+
} else if constexpr (std::is_integral_v<T> && std::is_integral_v<U>) {
19+
if constexpr (std::is_signed_v<T> == std::is_signed_v<U>) {
20+
if constexpr (std::numeric_limits<T>::max() >= std::numeric_limits<U>::max() &&
21+
std::numeric_limits<T>::lowest() <= std::numeric_limits<U>::lowest()) {
22+
return false;
23+
}
24+
} else if constexpr (std::is_signed_v<T> && std::is_unsigned_v<U>) {
25+
if constexpr (std::numeric_limits<T>::max() >= std::numeric_limits<U>::max()) {
26+
return false;
27+
}
28+
}
29+
}
30+
// signed -> unsigned with negative source: always narrowing
31+
// round-trip check can't catch this (int(-1) -> unsigned -> int(-1) is reversible on 2's complement)
32+
if constexpr (std::is_unsigned_v<T> && std::is_signed_v<U>) {
33+
if (u < 0) return true;
34+
}
35+
T t = static_cast<T>(u);
36+
if (static_cast<U>(t) != u) {
37+
return true;
38+
}
39+
if constexpr (std::is_floating_point_v<U> && std::is_integral_v<T>) {
40+
if (u != static_cast<U>(static_cast<long long>(u))) {
41+
return true;
42+
}
43+
}
44+
return false;
45+
}
46+
47+
template<typename T, typename U>
48+
constexpr T narrow_convert(U u) {
49+
if (would_narrow<T>(u)) {
50+
throw std::invalid_argument("narrowing conversion detected");
51+
}
52+
return static_cast<T>(u);
53+
}
54+
55+
template<typename T>
56+
class Number {
57+
T value_;
58+
public:
59+
template<typename U>
60+
constexpr Number(U u) : value_(narrow_convert<T>(u)) {}
61+
constexpr Number(T t) : value_(t) {}
62+
constexpr operator T() const noexcept { return value_; }
63+
constexpr T get() const noexcept { return value_; }
64+
};
65+
66+
int main() {
67+
int a = narrow_convert<int>(42.0);
68+
unsigned int b = narrow_convert<unsigned int>(100);
69+
std::cout << "a = " << a << ", b = " << b << "\n";
70+
71+
try { char c = narrow_convert<char>(300); }
72+
catch (const std::invalid_argument& e) { std::cout << "caught: " << e.what() << "\n"; }
73+
74+
try { unsigned int d = narrow_convert<unsigned int>(-1); }
75+
catch (const std::invalid_argument& e) { std::cout << "caught: " << e.what() << "\n"; }
76+
77+
Number<int> x = 42;
78+
Number<int> y = 3.0;
79+
std::cout << "x = " << x << ", y = " << y << "\n";
80+
81+
try { Number<char> c = 300; }
82+
catch (const std::invalid_argument& e) { std::cout << "caught: " << e.what() << "\n"; }
83+
84+
return 0;
85+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#include <iostream>
2+
#include <type_traits>
3+
#include <limits>
4+
#include <stdexcept>
5+
6+
template<typename T, typename U>
7+
constexpr bool would_narrow(U u) noexcept {
8+
if constexpr (std::is_same_v<T, U>) { return false; }
9+
else if constexpr (std::is_floating_point_v<U> && std::is_integral_v<T>) { }
10+
else if constexpr (std::is_floating_point_v<T> && std::is_floating_point_v<U>) {
11+
if constexpr (std::numeric_limits<T>::digits >= std::numeric_limits<U>::digits &&
12+
std::numeric_limits<T>::max() >= std::numeric_limits<U>::max() &&
13+
std::numeric_limits<T>::lowest() <= std::numeric_limits<U>::lowest()) {
14+
return false;
15+
}
16+
} else if constexpr (std::is_integral_v<T> && std::is_integral_v<U>) {
17+
if constexpr (std::is_signed_v<T> == std::is_signed_v<U>) {
18+
if constexpr (std::numeric_limits<T>::max() >= std::numeric_limits<U>::max() &&
19+
std::numeric_limits<T>::lowest() <= std::numeric_limits<U>::lowest()) { return false; }
20+
} else if constexpr (std::is_signed_v<T> && std::is_unsigned_v<U>) {
21+
if constexpr (std::numeric_limits<T>::max() >= std::numeric_limits<U>::max()) { return false; }
22+
}
23+
}
24+
// signed -> unsigned with negative source: always narrowing
25+
if constexpr (std::is_unsigned_v<T> && std::is_signed_v<U>) {
26+
if (u < 0) return true;
27+
}
28+
T t = static_cast<T>(u);
29+
if (static_cast<U>(t) != u) { return true; }
30+
if constexpr (std::is_floating_point_v<U> && std::is_integral_v<T>) {
31+
if (u != static_cast<U>(static_cast<long long>(u))) { return true; }
32+
}
33+
return false;
34+
}
35+
36+
template<typename T, typename U>
37+
constexpr T narrow_convert(U u) {
38+
if (would_narrow<T>(u)) throw std::invalid_argument("narrowing conversion detected");
39+
return static_cast<T>(u);
40+
}
41+
42+
template<typename T>
43+
class Number {
44+
T value_;
45+
public:
46+
template<typename U> constexpr Number(U u) : value_(narrow_convert<T>(u)) {}
47+
constexpr Number(T t) : value_(t) {}
48+
constexpr operator T() const noexcept { return value_; }
49+
constexpr T get() const noexcept { return value_; }
50+
template<typename U>
51+
constexpr auto operator+(const Number<U>& other) const -> Number<std::common_type_t<T, U>> {
52+
using R = std::common_type_t<T, U>;
53+
return Number<R>(value_ + other.get());
54+
}
55+
template<typename U>
56+
constexpr auto operator-(const Number<U>& other) const -> Number<std::common_type_t<T, U>> {
57+
using R = std::common_type_t<T, U>;
58+
return Number<R>(value_ - other.get());
59+
}
60+
template<typename U>
61+
constexpr auto operator*(const Number<U>& other) const -> Number<std::common_type_t<T, U>> {
62+
using R = std::common_type_t<T, U>;
63+
return Number<R>(value_ * other.get());
64+
}
65+
};
66+
67+
int main() {
68+
Number<int> a = 10;
69+
Number<double> b = 3.5;
70+
auto result = a + b;
71+
std::cout << "10 + 3.5 = " << result << "\n";
72+
std::cout << "result type is Number<double>? "
73+
<< std::is_same_v<decltype(result), Number<double>> << "\n";
74+
75+
Number<unsigned int> x = 3000000000u;
76+
Number<unsigned int> y = 2000000000u;
77+
// NOTE: unsigned arithmetic wraps before narrow_convert can check.
78+
// This does NOT throw — the result is 705032704 (a valid unsigned int).
79+
// See 01-06-overflow-not-caught.cpp for the full demonstration.
80+
auto wrapped = x + y;
81+
std::cout << "3000000000u + 2000000000u = " << wrapped
82+
<< " (wrapped, no exception)\n";
83+
return 0;
84+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* 验证:Number<T> 的算术溢出检测在 unsigned 类型上的局限
3+
*
4+
* 背景:文章原稿声称 Number<unsigned int>(3000000000u) + Number<unsigned int>(2000000000u)
5+
* 会抛出 std::invalid_argument 异常。但 unsigned 算术是 well-defined wrapping,
6+
* 结果在传给 narrow_convert 之前就已经回绕为合法值。
7+
*
8+
* 预期结果:不会抛异常,输出回绕后的值 705032704
9+
*
10+
* 编译命令:
11+
* g++ -std=c++20 -o /tmp/overflow-not-caught 01-06-overflow-not-caught.cpp
12+
*
13+
* 运行:
14+
* /tmp/overflow-not-caught
15+
*
16+
* 编译器:GCC 16.1.1
17+
*/
18+
19+
#include <iostream>
20+
#include <type_traits>
21+
#include <limits>
22+
#include <stdexcept>
23+
24+
template<typename T, typename U>
25+
constexpr bool would_narrow(U u) noexcept {
26+
if constexpr (std::is_same_v<T, U>) { return false; }
27+
else if constexpr (std::is_floating_point_v<U> && std::is_integral_v<T>) { }
28+
else if constexpr (std::is_floating_point_v<T> && std::is_floating_point_v<U>) {
29+
if constexpr (std::numeric_limits<T>::digits >= std::numeric_limits<U>::digits &&
30+
std::numeric_limits<T>::max() >= std::numeric_limits<U>::max() &&
31+
std::numeric_limits<T>::lowest() <= std::numeric_limits<U>::lowest()) {
32+
return false;
33+
}
34+
} else if constexpr (std::is_integral_v<T> && std::is_integral_v<U>) {
35+
if constexpr (std::is_signed_v<T> == std::is_signed_v<U>) {
36+
if constexpr (std::numeric_limits<T>::max() >= std::numeric_limits<U>::max() &&
37+
std::numeric_limits<T>::lowest() <= std::numeric_limits<U>::lowest()) { return false; }
38+
} else if constexpr (std::is_signed_v<T> && std::is_unsigned_v<U>) {
39+
if constexpr (std::numeric_limits<T>::max() >= std::numeric_limits<U>::max()) { return false; }
40+
}
41+
}
42+
// signed -> unsigned with negative source: always narrowing
43+
if constexpr (std::is_unsigned_v<T> && std::is_signed_v<U>) {
44+
if (u < 0) return true;
45+
}
46+
T t = static_cast<T>(u);
47+
if (static_cast<U>(t) != u) { return true; }
48+
if constexpr (std::is_floating_point_v<U> && std::is_integral_v<T>) {
49+
if (u != static_cast<U>(static_cast<long long>(u))) { return true; }
50+
}
51+
return false;
52+
}
53+
54+
template<typename T, typename U>
55+
constexpr T narrow_convert(U u) {
56+
if (would_narrow<T>(u)) throw std::invalid_argument("narrowing conversion detected");
57+
return static_cast<T>(u);
58+
}
59+
60+
template<typename T>
61+
class Number {
62+
T value_;
63+
public:
64+
template<typename U> constexpr Number(U u) : value_(narrow_convert<T>(u)) {}
65+
constexpr Number(T t) : value_(t) {}
66+
constexpr operator T() const noexcept { return value_; }
67+
constexpr T get() const noexcept { return value_; }
68+
template<typename U>
69+
constexpr auto operator+(const Number<U>& other) const -> Number<std::common_type_t<T, U>> {
70+
using R = std::common_type_t<T, U>;
71+
return Number<R>(value_ + other.get());
72+
}
73+
};
74+
75+
int main() {
76+
// unsigned 溢出测试:文章声称会抛异常
77+
Number<unsigned int> x = 3000000000u;
78+
Number<unsigned int> y = 2000000000u;
79+
80+
// 先看原始 unsigned 算术
81+
unsigned int raw_sum = 3000000000u + 2000000000u;
82+
std::cout << "Raw unsigned sum: " << raw_sum << "\n";
83+
std::cout << "Would narrow (same type)? " << would_narrow<unsigned int>(raw_sum) << "\n";
84+
85+
// 实际测试:不会抛异常
86+
try {
87+
auto overflow = x + y;
88+
std::cout << "No exception thrown! overflow = " << overflow << "\n";
89+
} catch (const std::invalid_argument& e) {
90+
std::cout << "Exception caught: " << e.what() << "\n";
91+
}
92+
93+
// 对比:真正的窄化转换确实能被捕获
94+
try {
95+
Number<short> s = narrow_convert<short>(40000);
96+
std::cout << "short = " << s << "\n";
97+
} catch (const std::invalid_argument& e) {
98+
std::cout << "Narrowing correctly caught: " << e.what() << "\n";
99+
}
100+
101+
return 0;
102+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#include <iostream>
2+
#include <functional>
3+
4+
template <typename T>
5+
struct safe_int {
6+
T value;
7+
friend safe_int operator+(const safe_int& a, const safe_int& b) {
8+
return safe_int{std::plus<T>{}(a.value, b.value)};
9+
}
10+
friend safe_int operator*(const safe_int& a, const safe_int& b) {
11+
return safe_int{std::multiplies<T>{}(a.value, b.value)};
12+
}
13+
};
14+
15+
int main() {
16+
safe_int<int> a{10}, b{20};
17+
auto c = a + b;
18+
auto d = a * b;
19+
std::cout << "10 + 20 = " << c.value << "\n";
20+
std::cout << "10 * 20 = " << d.value << "\n";
21+
return 0;
22+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#include <iostream>
2+
3+
int main() {
4+
int a = -1;
5+
unsigned int b = 2;
6+
std::cout << "(a < b) = " << (a < b) << "\n"; // prints 0 (false!) due to unsigned conversion
7+
return 0;
8+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#include <iostream>
2+
#include <type_traits>
3+
4+
template <typename T>
5+
struct safe_int {
6+
T value;
7+
};
8+
9+
template <typename T, typename U>
10+
bool operator<(const safe_int<T>& a, const safe_int<U>& b) {
11+
if constexpr (std::is_signed_v<T> && std::is_unsigned_v<U>) {
12+
if (a.value < 0) return true;
13+
return static_cast<std::make_unsigned_t<T>>(a.value) < b.value;
14+
} else if constexpr (std::is_unsigned_v<T> && std::is_signed_v<U>) {
15+
if (b.value < 0) return false;
16+
return a.value < static_cast<std::make_unsigned_t<U>>(b.value);
17+
} else {
18+
return a.value < b.value;
19+
}
20+
}
21+
22+
int main() {
23+
safe_int<int> a{-1};
24+
safe_int<unsigned int> b{2};
25+
std::cout << "(a < b) = " << (a < b) << "\n"; // Now correctly prints 1 (true)
26+
return 0;
27+
}

0 commit comments

Comments
 (0)