Skip to content

Commit 3549bce

Browse files
committed
Align cpp demos with docs 11-20 (endianness through concepts)
- Create src/big_endian_little_endian.cpp: runtime + std::endian detection, manual + std::byteswap fallback, CAN/J1939 big-endian encode, ROS network port byteswap, SPI IMU two's-complement decode via std::bit_cast. - Rewrite src/bitset_bit_field.cpp to add the bit-field section (MotorStatus packed status word, CanFlags 1-byte CAN layout). Wire dead demos into main in src/bitwise_operations.cpp; add sensor status register and C++20 std::bit_xor/and/or. - Rewrite src/buffer_overflow.cpp: gate unsafe strcpy demo behind --unsafe, add bounds-check / strncpy / dynamic-alloc / std::string / span row-stride prevention demos. - Rewrite src/bind.cpp around log_event + Motor::set_torque; expand src/callbacks.cpp with Robot/planner/IMU/Signal/raw-fn-pointer demos. - Rewrite src/class/cast_Base_to_Derived_to_Base.cpp section-by-section matching the doc; fix two leaks, add Sensor/LidarSensor/ImuSensor SLAM dispatcher. - Create src/class/class_forward_declaration.cpp (was empty 0-byte file): Controller-holds-Robot* case, IMUSensor:Sensor inheritance, Robot-by-value. - Create src/class/special_member_functions.cpp: SensorBuffer owning a heap float[], exercises all six SMFs with logging. - Create src/concepts.cpp: Addable, Container with all four `requires` kinds, four ways to use a concept, std::integral/floating_point/range, overload resolution by constraints, concepts-vs-if-constexpr, Sensor concept with IMU/Encoder. - CMakeLists: add big_endian_little_endian, class_forward_declaration, special_member_functions, concepts executables. - docs/class_special_member_functions.md: add link to the new runnable demo.
1 parent ec7c13a commit 3549bce

12 files changed

Lines changed: 1540 additions & 243 deletions

CMakeLists.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ add_executable(operator_overloading src/class/operator_overloading.cpp)
107107

108108
add_executable(default_0_delete_meaning src/class/default_0_delete_meaning.cpp)
109109

110+
add_executable(special_member_functions src/class/special_member_functions.cpp)
111+
110112
add_executable(object_slicing src/class/object_slicing.cpp)
111113

112114
add_executable(static_member_function src/class/static_member_function.cpp)
@@ -115,6 +117,8 @@ add_executable(constructor_initialization_list src/class/constructor_initializat
115117

116118
add_executable(inheritance src/class/inheritance.cpp)
117119

120+
add_executable(class_forward_declaration src/class/class_forward_declaration.cpp)
121+
118122
add_executable(switch_case src/switch_case.cpp)
119123

120124
add_executable(set_map_pair_tuple src/set_map_pair_tuple.cpp)
@@ -123,6 +127,8 @@ add_executable(error_handling src/error_handling.cpp)
123127

124128
add_executable(bitwise_operations src/bitwise_operations.cpp)
125129

130+
add_executable(big_endian_little_endian src/big_endian_little_endian.cpp)
131+
126132
add_executable(bitset_bit_field src/bitset_bit_field.cpp)
127133

128134
add_executable(hash src/hash.cpp)
@@ -149,6 +155,8 @@ target_link_libraries(RAII ${THREADING_LIB})
149155

150156
add_executable(SFINAE src/SFINAE.cpp)
151157

158+
add_executable(concepts src/concepts.cpp)
159+
152160
add_executable(type_erasure src/type_erasure.cpp)
153161

154162
add_executable(buffer_overflow src/buffer_overflow.cpp)

docs/class_special_member_functions.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,13 @@ In this example, `ResourceOwner` manages a dynamic integer. The copy constructor
138138
139139
![special member functions](diagrams/uml_diagrams/special-member-functions.png "special member functions")
140140
141+
Runnable demo: [`src/class/special_member_functions.cpp`](../src/class/special_member_functions.cpp) — a `SensorBuffer` that owns a heap `float[]` (think one LIDAR frame). Each SMF logs which instance is doing what, so you can see deep-copy vs pointer-steal and the destructor order at end of `main`.
141142
143+
Build & run:
144+
145+
```sh
146+
cmake --build build --target special_member_functions -j 4
147+
./build/Debug/special_member_functions # or Release/, depending on config
148+
```
142149

143150
Refs: [1](https://www.foonathan.net/2019/02/special-member-functions/), [2](https://howardhinnant.github.io/)

src/big_endian_little_endian.cpp

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
// Companion source for docs/big-endian_little-endian.md
2+
//
3+
// Demonstrates byte-order techniques referenced in the doc:
4+
// 1. Runtime endian detection (the classic pointer-cast trick).
5+
// 2. Compile-time endian detection via std::endian (C++20).
6+
// 3. Printing raw memory bytes vs. logical big-endian bytes.
7+
// 4. Manual byte swapping for 16/32/64-bit words.
8+
// 5. std::byteswap (C++23) when available, else a manual fallback.
9+
// 6. Robotics-flavored examples: CAN frame ID + payload, network
10+
// protocol header on the wire, and an embedded sensor word.
11+
//
12+
// Build target: big_endian_little_endian (C++20).
13+
14+
#include <array>
15+
#include <bit> // std::endian, std::bit_cast
16+
#include <cstdint>
17+
#include <cstring> // std::memcpy
18+
#include <iomanip>
19+
#include <iostream>
20+
#include <span>
21+
#include <string_view>
22+
23+
#include <version> // feature-test macros (__cpp_lib_byteswap)
24+
25+
// --- Helpers -----------------------------------------------------------------
26+
27+
// Print bytes of any trivially-copyable value as stored in memory.
28+
template <class T>
29+
void print_memory_bytes(const T& value, std::string_view label) {
30+
const auto* p = reinterpret_cast<const unsigned char*>(&value);
31+
std::cout << label << " (in-memory): ";
32+
std::cout << std::hex << std::setfill('0');
33+
for (std::size_t i = 0; i < sizeof(T); ++i) {
34+
std::cout << std::setw(2) << static_cast<int>(p[i]) << ' ';
35+
}
36+
std::cout << std::dec << std::setfill(' ') << '\n';
37+
}
38+
39+
// Print bytes from MSB to LSB (logical big-endian view) regardless of host.
40+
template <class T>
41+
void print_logical_be(const T& value, std::string_view label) {
42+
const auto* p = reinterpret_cast<const unsigned char*>(&value);
43+
std::cout << label << " (logical BE): ";
44+
std::cout << std::hex << std::setfill('0');
45+
if constexpr (std::endian::native == std::endian::little) {
46+
for (std::size_t i = sizeof(T); i-- > 0;) {
47+
std::cout << std::setw(2) << static_cast<int>(p[i]) << ' ';
48+
}
49+
} else {
50+
for (std::size_t i = 0; i < sizeof(T); ++i) {
51+
std::cout << std::setw(2) << static_cast<int>(p[i]) << ' ';
52+
}
53+
}
54+
std::cout << std::dec << std::setfill(' ') << '\n';
55+
}
56+
57+
// Classic runtime check (mirrors the doc's isBigEndian()).
58+
bool runtime_is_big_endian() {
59+
std::uint32_t test = 1;
60+
const auto* byte = reinterpret_cast<const unsigned char*>(&test);
61+
return byte[0] == 0;
62+
}
63+
64+
// Portable byte swaps. Used as a fallback when std::byteswap is unavailable.
65+
constexpr std::uint16_t bswap16(std::uint16_t v) noexcept {
66+
return static_cast<std::uint16_t>((v << 8) | (v >> 8));
67+
}
68+
constexpr std::uint32_t bswap32(std::uint32_t v) noexcept {
69+
return ((v & 0x000000FFu) << 24) |
70+
((v & 0x0000FF00u) << 8) |
71+
((v & 0x00FF0000u) >> 8) |
72+
((v & 0xFF000000u) >> 24);
73+
}
74+
constexpr std::uint64_t bswap64(std::uint64_t v) noexcept {
75+
return ((v & 0x00000000000000FFull) << 56) |
76+
((v & 0x000000000000FF00ull) << 40) |
77+
((v & 0x0000000000FF0000ull) << 24) |
78+
((v & 0x00000000FF000000ull) << 8) |
79+
((v & 0x000000FF00000000ull) >> 8) |
80+
((v & 0x0000FF0000000000ull) >> 24) |
81+
((v & 0x00FF000000000000ull) >> 40) |
82+
((v & 0xFF00000000000000ull) >> 56);
83+
}
84+
85+
// host <-> big-endian conversion (no-op on big-endian hosts).
86+
template <class T>
87+
constexpr T host_to_be(T v) noexcept {
88+
static_assert(std::is_integral_v<T>, "integral only");
89+
if constexpr (std::endian::native == std::endian::big) {
90+
return v;
91+
} else {
92+
if constexpr (sizeof(T) == 1) return v;
93+
else if constexpr (sizeof(T) == 2) return static_cast<T>(bswap16(static_cast<std::uint16_t>(v)));
94+
else if constexpr (sizeof(T) == 4) return static_cast<T>(bswap32(static_cast<std::uint32_t>(v)));
95+
else if constexpr (sizeof(T) == 8) return static_cast<T>(bswap64(static_cast<std::uint64_t>(v)));
96+
else static_assert(sizeof(T) == 0, "unsupported size");
97+
}
98+
}
99+
100+
template <class T>
101+
constexpr T be_to_host(T v) noexcept { return host_to_be(v); } // symmetric
102+
103+
void banner(std::string_view title) {
104+
std::cout << "\n=== " << title << " ===\n";
105+
}
106+
107+
// --- Demos -------------------------------------------------------------------
108+
109+
void demo_runtime_detection() {
110+
banner("1. Runtime endian detection (doc's classic trick)");
111+
std::cout << "runtime_is_big_endian(): "
112+
<< (runtime_is_big_endian() ? "Big-endian" : "Little-endian") << '\n';
113+
}
114+
115+
void demo_compile_time_detection() {
116+
banner("2. Compile-time detection via std::endian (C++20)");
117+
if constexpr (std::endian::native == std::endian::little) {
118+
std::cout << "std::endian::native == little\n";
119+
} else if constexpr (std::endian::native == std::endian::big) {
120+
std::cout << "std::endian::native == big\n";
121+
} else {
122+
std::cout << "std::endian::native == mixed/unknown\n";
123+
}
124+
}
125+
126+
void demo_byte_layout() {
127+
banner("3. Byte layout of 0x12345678 (doc's printBytes/printBigEndianBytes)");
128+
std::uint32_t number = 0x12345678u;
129+
std::cout << "value = 0x" << std::hex << number << std::dec << '\n';
130+
print_memory_bytes(number, " number");
131+
print_logical_be (number, " number");
132+
}
133+
134+
void demo_byteswap() {
135+
banner("4. Byte swapping (manual + std::byteswap if available)");
136+
std::uint32_t v32 = 0xDEADBEEFu;
137+
std::uint16_t v16 = 0xABCDu;
138+
std::uint64_t v64 = 0x0123456789ABCDEFull;
139+
140+
std::cout << std::hex << std::setfill('0');
141+
std::cout << "manual bswap16(0x" << std::setw(4) << v16 << ") = 0x"
142+
<< std::setw(4) << bswap16(v16) << '\n';
143+
std::cout << "manual bswap32(0x" << std::setw(8) << v32 << ") = 0x"
144+
<< std::setw(8) << bswap32(v32) << '\n';
145+
std::cout << "manual bswap64(0x" << std::setw(16) << v64 << ") = 0x"
146+
<< std::setw(16) << bswap64(v64) << '\n';
147+
std::cout << std::dec << std::setfill(' ');
148+
149+
#if defined(__cpp_lib_byteswap) && __cpp_lib_byteswap >= 202110L
150+
std::cout << "std::byteswap available (__cpp_lib_byteswap = "
151+
<< __cpp_lib_byteswap << ")\n";
152+
std::cout << std::hex << std::setfill('0');
153+
std::cout << " std::byteswap<uint32_t>(0xDEADBEEF) = 0x"
154+
<< std::setw(8) << std::byteswap(v32) << '\n';
155+
std::cout << std::dec << std::setfill(' ');
156+
#else
157+
std::cout << "std::byteswap NOT available on this toolchain (needs C++23).\n"
158+
<< " Falling back to manual bswap32/16/64 above.\n";
159+
#endif
160+
}
161+
162+
// --- Robotics framing --------------------------------------------------------
163+
164+
// A CAN 2.0B frame layout (simplified): 29-bit extended ID + up to 8 data bytes.
165+
// The CAN bus itself transmits bits, but when a controller hands you a frame
166+
// struct you often need to interpret the payload as a multi-byte integer with
167+
// a documented endianness (CANopen is little-endian, J1939 signals are LE too,
168+
// but many OEM protocols pack big-endian "Motorola" signals).
169+
struct CanFrame {
170+
std::uint32_t id; // host order
171+
std::uint8_t dlc; // data length code, 0..8
172+
std::array<std::uint8_t, 8> data; // raw payload bytes
173+
};
174+
175+
void demo_can_frame() {
176+
banner("5. Robotics: CAN frame with a big-endian (Motorola) signal");
177+
// Suppose byte 0..3 of the payload carry a 32-bit wheel-encoder count in
178+
// big-endian order, byte 4..5 a 16-bit motor temperature in BE, deg*100.
179+
std::uint32_t encoder_counts = 0x0001F4A0u; // 128'160 counts
180+
std::uint16_t motor_temp_cdg = 0x1F40u; // 80.00 deg C (8000)
181+
182+
CanFrame f{};
183+
f.id = 0x18FF50E5u; // J1939-style 29-bit extended ID
184+
f.dlc = 6;
185+
186+
// Pack signals into the wire (big-endian).
187+
std::uint32_t enc_be = host_to_be(encoder_counts);
188+
std::uint16_t temp_be = host_to_be(motor_temp_cdg);
189+
std::memcpy(f.data.data() + 0, &enc_be, sizeof(enc_be));
190+
std::memcpy(f.data.data() + 4, &temp_be, sizeof(temp_be));
191+
192+
std::cout << "CAN id = 0x" << std::hex << f.id << std::dec
193+
<< ", dlc = " << static_cast<int>(f.dlc) << '\n';
194+
std::cout << "payload bytes (on wire): " << std::hex << std::setfill('0');
195+
for (std::uint8_t i = 0; i < f.dlc; ++i) {
196+
std::cout << std::setw(2) << static_cast<int>(f.data[i]) << ' ';
197+
}
198+
std::cout << std::dec << std::setfill(' ') << '\n';
199+
200+
// Decode side: pull bytes off the wire and convert back to host order.
201+
std::uint32_t enc_wire = 0;
202+
std::uint16_t temp_wire = 0;
203+
std::memcpy(&enc_wire, f.data.data() + 0, sizeof(enc_wire));
204+
std::memcpy(&temp_wire, f.data.data() + 4, sizeof(temp_wire));
205+
std::cout << "decoded encoder counts = " << be_to_host(enc_wire) << '\n';
206+
std::cout << "decoded motor temp = "
207+
<< (be_to_host(temp_wire) / 100.0) << " degC\n";
208+
}
209+
210+
void demo_network_header() {
211+
banner("6. Robotics: network protocol header (network byte order = BE)");
212+
// ROS 1 TCPROS, custom UDP telemetry, and most IP-layer protocols use
213+
// big-endian on the wire. htons/htonl exist precisely for this.
214+
std::uint16_t host_port = 11311; // ROS master default port
215+
std::uint16_t wire_port = host_to_be(host_port);
216+
217+
std::cout << "host port : " << host_port
218+
<< " (0x" << std::hex << host_port << std::dec << ")\n";
219+
std::cout << "wire port : 0x" << std::hex << wire_port << std::dec << '\n';
220+
print_memory_bytes(host_port, " host_port");
221+
print_memory_bytes(wire_port, " wire_port");
222+
}
223+
224+
void demo_embedded_sensor_word() {
225+
banner("7. Robotics: embedded sensor word with std::bit_cast");
226+
// A SPI IMU often returns a 16-bit two's-complement reading MSB-first.
227+
// Bytes arrive as {hi, lo}; reinterpret without aliasing UB.
228+
std::array<std::uint8_t, 2> rx_buffer{0xFF, 0x80}; // -128 raw count, BE
229+
230+
std::uint16_t raw_be{};
231+
std::memcpy(&raw_be, rx_buffer.data(), sizeof(raw_be));
232+
std::uint16_t raw_host = be_to_host(raw_be);
233+
auto signed_count = std::bit_cast<std::int16_t>(raw_host);
234+
235+
std::cout << "rx bytes : 0x" << std::hex << static_cast<int>(rx_buffer[0])
236+
<< ' ' << std::hex << static_cast<int>(rx_buffer[1]) << std::dec << '\n';
237+
std::cout << "raw_host : 0x" << std::hex << raw_host << std::dec << '\n';
238+
std::cout << "signed : " << signed_count << " counts\n";
239+
}
240+
241+
// --- main --------------------------------------------------------------------
242+
243+
int main() {
244+
std::cout << "Endianness demos (companion to docs/big-endian_little-endian.md)\n";
245+
246+
demo_runtime_detection();
247+
demo_compile_time_detection();
248+
demo_byte_layout();
249+
demo_byteswap();
250+
demo_can_frame();
251+
demo_network_header();
252+
demo_embedded_sensor_word();
253+
254+
std::cout << "\nDone.\n";
255+
return 0;
256+
}

0 commit comments

Comments
 (0)