{"id":50338693,"url":"https://github.com/tigran-sargsyan-w/cpp-module-06","last_synced_at":"2026-05-29T15:30:21.495Z","repository":{"id":338100346,"uuid":"1148343674","full_name":"tigran-sargsyan-w/cpp-module-06","owner":"tigran-sargsyan-w","description":"This module is designed to help you understand the different types of casting in C++.","archived":false,"fork":false,"pushed_at":"2026-04-26T14:36:42.000Z","size":38,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-06T11:49:04.057Z","etag":null,"topics":["42","42lyon","42school","cpp","cpp-modules","cpp98","dynamic-cast","inheritance","object-oriented-programming","oop","polymorphism","reinterpret-cast","rtti","scalar-conversion","serialization","static-cast","type-casting"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tigran-sargsyan-w.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-02T21:21:53.000Z","updated_at":"2026-04-26T14:36:45.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/tigran-sargsyan-w/cpp-module-06","commit_stats":null,"previous_names":["tigran-sargsyan-w/cpp-module-06"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/tigran-sargsyan-w/cpp-module-06","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tigran-sargsyan-w%2Fcpp-module-06","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tigran-sargsyan-w%2Fcpp-module-06/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tigran-sargsyan-w%2Fcpp-module-06/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tigran-sargsyan-w%2Fcpp-module-06/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tigran-sargsyan-w","download_url":"https://codeload.github.com/tigran-sargsyan-w/cpp-module-06/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tigran-sargsyan-w%2Fcpp-module-06/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33659872,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-29T02:00:06.066Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["42","42lyon","42school","cpp","cpp-modules","cpp98","dynamic-cast","inheritance","object-oriented-programming","oop","polymorphism","reinterpret-cast","rtti","scalar-conversion","serialization","static-cast","type-casting"],"created_at":"2026-05-29T15:30:20.825Z","updated_at":"2026-05-29T15:30:21.464Z","avatar_url":"https://github.com/tigran-sargsyan-w.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# C++ Module 06 – C++ Casts 🔁🧠\n\n✅ **Status**: Completed – all exercises  \n🏫 **School**: 42 – C++ Modules (Module 06)  \n🏅 **Score**: 90/100 (One of the reviewers did not agree with the scalar converter solution🤔)\n\n\u003e *Scalar conversion, pointer serialization via `reinterpret_cast`, and runtime type identification via `dynamic_cast` (without `\u003ctypeinfo\u003e`).*\n\n---\n\n## 📚 Table of Contents\n\n* [Description](#-description)\n* [Goals of the Module](#-goals-of-the-module)\n* [Exercises Overview](#-exercises-overview)\n\n  * [ex00 – ScalarConverter](#ex00--scalarconverter)\n  * [ex01 – Serializer](#ex01--serializer)\n  * [ex02 – Identify real type](#ex02--identify-real-type)\n* [Requirements](#-requirements)\n* [Build \u0026 Run](#-build--run)\n* [Repository Layout](#-repository-layout)\n* [ex00 Notes](#ex00-notes)\n\n  * [🔥 Important Edge Case: float rounding near INT limits](#-important-edge-case-float-rounding-near-int-limits)\n  * [🧩 Clarification: Why `char: Non displayable` for 128 and not always `impossible`](#-clarification-why-char-non-displayable-for-128-and-not-always-impossible)\n* [ex01 Notes](#ex01-notes)\n\n  * [🔥 Important Concept: Pointer Size \u0026 `uintptr_t`](#-important-concept-pointer-size--uintptr_t)\n  * [🔍 Why `reinterpret_cast` is required here](#-why-reinterpret_cast-is-required-here)\n  * [⚠️ Lifetime vs Address](#️-lifetime-vs-address)\n* [ex02 Notes](#ex02-notes)\n\n  * [🧠 Identify Real Type – Critical Nuances](#-identify-real-type--critical-nuances)\n* [🔍 Testing Tips](#-testing-tips)\n* [🧾 42 Notes](#-42-notes)\n\n---\n\n## 📝 Description\n\nThis repository contains my solutions to **42’s C++ Module 06 (C++98)**.\nThe module is a deep dive into **casting and type conversions**:\n\n* converting strings into scalars (`ex00`)\n* converting pointers into integers and back (`ex01`)\n* identifying the real dynamic type behind a base pointer/reference (`ex02`)\n\nA big part of this module is not only making it work, but also understanding **why a specific cast is the correct tool**.\n\n---\n\n## 🎯 Goals of the Module\n\nConcepts practiced:\n\n* Correct use of C++ casts: `static_cast`, `reinterpret_cast`, `dynamic_cast`\n* Handling pseudo literals (`nan`, `inf`, with and without `f`) in scalar conversions\n* Edge cases: overflow, precision loss, non-displayable characters\n* Why `uintptr_t` exists (portable “integer big enough to hold an address”)\n* RTTI-style detection with `dynamic_cast` without using `\u003ctypeinfo\u003e`\n\n---\n\n## 📦 Exercises Overview\n\n### ex00 – ScalarConverter\n\n**Goal:** Implement a non-instantiable class `ScalarConverter` with:\n\n* `static void convert(std::string const\u0026 literal);`\n\nIt detects the literal type, converts it to the “real” type first, then prints:\n\n* `char`\n* `int`\n* `float`\n* `double`\n\nMust handle pseudo literals:\n\n* `nan`, `+inf`, `-inf`\n* `nanf`, `+inff`, `-inff`\n\n---\n\n### ex01 – Serializer\n\n**Goal:** Implement a non-instantiable class `Serializer` with:\n\n* `static uintptr_t serialize(Data* ptr);`\n* `static Data* deserialize(uintptr_t raw);`\n\nCreate a non-empty `Data` struct and verify that:\n\n* serializing and deserializing gives the **same address** back\n* pointer equality is preserved\n\n---\n\n### ex02 – Identify real type\n\n**Goal:** Create:\n\n* `Base` (only a public virtual destructor)\n* `A`, `B`, `C` inheriting publicly from `Base`\n\nImplement:\n\n* `Base* generate();` (randomly returns new `A/B/C`)\n* `void identify(Base* p);`\n* `void identify(Base\u0026 p);` (**no pointer usage inside this one**)\n\nForbidden:\n\n* `\u003ctypeinfo\u003e` / `typeid`\n\n---\n\n## 🛠 Requirements\n\n* **Compiler**: `c++`\n* **Flags**: `-Wall -Wextra -Werror -std=c++98`\n* **No external libraries** (no C++11+)\n* **No** `printf`, `malloc`, `free` (and friends)\n\n---\n\n## ▶️ Build \u0026 Run\n\n```bash\ngit clone \u003cthis-repo-url\u003e\ncd cpp-module-06\n```\n\n### ex00\n\n```bash\ncd ex00\nmake\n./convert 0\n./convert 42.0f\n./convert nan\n```\n\n### ex01\n\n```bash\ncd ex01\nmake\n./serializer\n```\n\n### ex02\n\n```bash\ncd ex02\nmake\n./identify\n```\n\n---\n\n## 📂 Repository Layout\n\n```text\ncpp-module-06/\n├── ex00/\n│   ├── Makefile\n│   ├── ScalarConverter.hpp\n│   ├── ScalarConverter.cpp\n│   └── main.cpp\n│\n├── ex01/\n│   ├── Makefile\n│   ├── Serializer.hpp\n│   ├── Serializer.cpp\n│   ├── Data.hpp\n│   └── main.cpp\n│\n└── ex02/\n    ├── Makefile\n    ├── Base.hpp\n    ├── Base.cpp\n    ├── A.hpp / A.cpp\n    ├── B.hpp / B.cpp\n    ├── C.hpp / C.cpp\n    └── main.cpp\n```\n\n---\n\n# ex00 Notes\n\n## 🔥 Important Edge Case: float rounding near INT limits\n\nSometimes you’ll see:\n\n```bash\n./convert 2147483647\n```\n\nand the output includes:\n\n```\nint: 2147483647\nfloat: 2147483648.0f\n```\n\nThis is **expected**.\n\n### Why it happens\n\n`float` has only **24 significant bits**, so near `2^31` it cannot represent every integer.\n\nAt around `2^31`:\n\n* `2147483648 = 2^31`\n* spacing between neighboring float values becomes:\n\n**ULP = 2^(31 − 23) = 2^8 = 256**\n\nSo floats there are effectively “grid points” spaced by **256**.\n\nClosest representable values around `2^31`:\n\n* `2147483392 = 2147483648 − 256`\n* `2147483648`\n\nMidpoint:\n\n* `2147483392 + 128 = 2147483520`\n\nSo:\n\n* `2147483392 ... 2147483519` → rounds to `2147483392`\n* `2147483520 ... 2147483647` → rounds to `2147483648`\n\nNegative values behave symmetrically.\n\n---\n\n## 🧩 Clarification: Why `char: Non displayable` for 128 and not always `impossible`\n\nYou might notice:\n\n```\nInput: 128.0\nchar: Non displayable\n```\n\nand wonder if it should be `impossible`.\n\n### 1) What a `char` really is in C++\n\nIn C/C++, **`char` is just a 1-byte integer type**.\n\nTwo important properties:\n\n1. `sizeof(char) == 1` always.\n2. Whether `char` is **signed** or **unsigned** is **implementation-defined**.\n\n* On some systems: `char` behaves like `signed char` (range typically `-128..127`)\n* On others: it behaves like `unsigned char` (range typically `0..255`)\n\nSo when we ask: “can we convert to `char`?” — the answer depends on **which rule we choose**.\n\n### 2) The subject usually wants *ASCII-like* printing\n\nMost 42 solutions treat the `char` output as:\n\n* Valid range: **0..127** (ASCII)\n* Printable: **32..126**\n* Control / DEL: `0..31` and `127` → `Non displayable`\n* Everything outside `0..127` → `impossible`\n\nUnder that rule:\n\n* `128` would be **`impossible`**\n\n✅ This is the safest choice if your evaluator/tester expects strict ASCII.\n\n### 3) Why some implementations print `Non displayable` for 128\n\nIf your implementation checks the range using `unsigned char` limits (`0..255`), then:\n\n* `128` is considered “representable as a byte” → so it’s **not impossible**\n* but it’s not in printable ASCII (`32..126`) → so you print **Non displayable**\n\nThat logic is consistent from a *C++ type system* point of view.\n\n### 4) Where encodings come into play (and why it’s tricky)\n\nThe reason `128` is a headache is not C++ casting — it’s **text encoding**:\n\n* ASCII defines only **0..127**.\n* Values **128..255** are **not ASCII**.\n* Historically there were many “extended ASCII” code pages (Windows-1252, ISO-8859-1, KOI8-R, ...).\n* Modern terminals often use **UTF-8**, where a single byte `0x80` is not a valid standalone character.\n\nSo printing a raw byte with value 128 can:\n\n* show a weird symbol\n* show a replacement character\n* visually break output\n\n✅ That’s why the most evaluator-safe choice is usually: treat `char` output as ASCII-only.\n\n### Recommended rule for maximum safety\n\n* if value \u003c 0 or value \u003e 127 → `impossible`\n* else if value \u003c 32 or value == 127 → `Non displayable`\n* else → print `'c'`\n\n---\n\n# ex01 Notes\n\n## 🔥 Important Concept: Pointer Size \u0026 `uintptr_t`\n\nIn **ex01 (Serialization)** we convert a pointer into an integer and back:\n\n```cpp\nuintptr_t raw = Serializer::serialize(ptr);\nData* again = Serializer::deserialize(raw);\n```\n\nAt first glance this looks trivial — but there is an important architectural detail behind it.\n\n### 🧠 Why pointer size matters\n\nA pointer is just an address in memory.\nBut the **size of that address depends on the machine architecture**.\n\n* **32-bit systems**: pointer size is 32 bits (4 GB address space)\n* **16-bit systems** (very old): pointer size is 16 bits (64 KB address space)\n* **64-bit systems** (modern standard): pointer size is 64 bits\n* **specialized / experimental** architectures may even use **128-bit** addressing\n\nHardcoding a type like `unsigned long` is not portable: on some platforms it might be too small.\n\n### ✅ Why we use `uintptr_t`\n\n`uintptr_t` (from `\u003cstdint.h\u003e`, C++98 compatible) is:\n\n\u003e an unsigned integer type capable of storing a pointer value without loss.\n\nThis means:\n\n* On 16-bit systems → `uintptr_t` is 16 bits\n* On 32-bit systems → `uintptr_t` is 32 bits\n* On 64-bit systems → `uintptr_t` is 64 bits\n* On 128-bit systems → it would match 128 bits\n\nThe compiler chooses the correct size automatically for the target architecture.\n\nSo instead of guessing pointer size, we rely on a **portable, architecture-safe type**.\n\n### ⚠️ Important distinction\n\nUsing `uintptr_t`:\n\n* ✔ guarantees we do not lose address bits\n* ✔ makes the conversion reversible\n* ✔ keeps the code portable\n\nBut it does **not**:\n\n* ❌ extend object lifetime\n* ❌ make a destroyed object valid again\n* ❌ protect against dangling pointers\n\n`uintptr_t` safely stores an address.\nIt does not guarantee that an object still exists at that address.\n---\n\n## 🔍 Why `reinterpret_cast` is required here\n\nIn this exercise we need to convert between:\n\n* a **pointer type** (`Data*`)\n* an **integer type** (`uintptr_t`)\n\nThis is a low-level reinterpretation of the same bit pattern.\n\n### Why not `static_cast`?\n\n`static_cast` is for well-defined conversions like numeric conversions and safe up/down casts in inheritance.\nIt’s **not meant** for arbitrary pointer ↔ integer conversions.\n\n### Why `reinterpret_cast`?\n\n`reinterpret_cast` is the cast designed for:\n\n* treating an address value as an integer\n* treating an integer as an address value\n\nSo in ex01 we do:\n\n```cpp\nreturn reinterpret_cast\u003cuintptr_t\u003e(ptr);\n```\n\nand the reverse:\n\n```cpp\nreturn reinterpret_cast\u003cData*\u003e(raw);\n```\n\nImportant: this cast does not validate anything — it only reinterprets.\n\n---\n\n## ⚠️ Lifetime vs Address\n\nA core nuance of ex01 is understanding the difference between:\n\n* **An address** (just a number)\n* **A live object** (valid memory with a valid lifetime)\n\n### ✅ The address can be stored safely\n\n`uintptr_t` can store the pointer value without losing bits.\nSo the conversion is reversible:\n\n```cpp\nData* again = Serializer::deserialize(Serializer::serialize(ptr));\n```\n\n### ❌ But the object may no longer exist\n\nIf the original object’s lifetime ends, the address still exists as a number,\nbut dereferencing it becomes **Undefined Behavior**.\n\nTypical UB situations:\n\n* stack object leaves scope → dangling pointer\n* heap object is deleted → use-after-free\n\n\u003e `uintptr_t` preserves the address correctly, but it cannot preserve the object.\n\n---\n\n# ex02 Notes\n\n## 🧠 Identify Real Type – Critical Nuances\n\nThis exercise is about **polymorphism**, **RTTI behavior**, and correct use of `dynamic_cast`.\n\n---\n\n### 🔥 Base MUST Be Polymorphic\n\n```cpp\nclass Base\n{\npublic:\n    virtual ~Base();\n};\n```\n\nWhy this is mandatory:\n\n* `dynamic_cast` works correctly **only with polymorphic types**\n* a class becomes polymorphic if it has at least one `virtual` function\n\nAlso, deleting through base pointer must be safe:\n\n```cpp\nBase* ptr = new A();\ndelete ptr;\n```\n\nWithout a virtual destructor:\n\n* ❌ only `Base` destructor runs\n* ❌ derived destructor does not run\n* ❌ UB / partial destruction\n\nSo the virtual destructor guarantees:\n\n* ✔ RTTI works\n* ✔ proper destruction through base pointer\n\n---\n\n### 🔄 Implicit Upcasting in `generate()`\n\n```cpp\nreturn new A();\n```\n\nThe function returns `Base*`, so this triggers an **implicit upcast**:\n\n* `A* → Base*`\n\nEquivalent to:\n\n```cpp\nA* ptrA = new A();\nBase* ptrBase = ptrA;\nreturn ptrBase;\n```\n\nThis is safe because inheritance is `public`.\n\n✅ Not slicing: there is no slicing because we use pointers.\n\n---\n\n### ⚠️ Pointer Adjustment (Subtle but Important)\n\nIn simple single inheritance, `A*` and `Base*` often have the same address.\nBut with multiple inheritance:\n\n* `Base*` may require an internal offset\n* the compiler automatically adjusts the pointer\n\nUpcasting may internally modify the address to point to the correct base subobject.\n\n---\n\n### 🔎 `dynamic_cast` behavior\n\n**Pointer version**:\n\n```cpp\ndynamic_cast\u003cA*\u003e(p);\n```\n\n* ✔ on failure: returns `NULL`\n* ✔ on success: returns valid pointer\n\n**Reference version**:\n\n```cpp\ndynamic_cast\u003cA\u0026\u003e(p);\n```\n\n* ❗ on failure: throws `std::bad_cast`\n\nSo the reference-based identify must use `try/catch`.\n\n**Critical difference**:\n\n| Cast form          | On failure       |\n| ------------------ | ---------------- |\n| `dynamic_cast\u003cT*\u003e` | returns `NULL`   |\n| `dynamic_cast\u003cT\u0026\u003e` | throws exception |\n\nReason:\n\n* a pointer may legally be `NULL`\n* a reference must always refer to a valid object\n\n---\n\n### 🚫 Why `static_cast` is wrong here (downcasting)\n\n```cpp\nA* a = static_cast\u003cA*\u003e(basePtr);\n```\n\nIf `basePtr` actually points to `B`:\n\n* ❌ Undefined Behavior\n* ❌ no runtime check is performed\n\nOnly `dynamic_cast` verifies the real runtime type.\n\n---\n### 🔬 What Happens \"Under the Hood\" (Upcast Internals)\n\nWhen we write:\n\n```cpp\nreturn new A();\n```\n\nbut the function returns `Base*`, an **implicit upcast** happens:\n\n```\nA* → Base*\n```\n\nThis is NOT packaging or wrapping.\n\nIt is a compile-time conversion allowed because of `public` inheritance.\n\nConceptually equivalent to:\n\n```cpp\nA* ptrA = new A();\nBase* ptrBase = ptrA;\nreturn ptrBase;\n```\n\n### ⚠️ Important detail\n\nIn simple single inheritance the addresses are usually identical.\n\nWith multiple inheritance, the compiler may apply an **internal pointer offset adjustment** so that the `Base*` points to the correct base subobject.\n\nNo object slicing occurs because we are using pointers.\n\n---\n\n## 🚫 Why Not `static_cast`?\n\n```cpp\nA* a = static_cast\u003cA*\u003e(basePtr);\n```\n\nIf `basePtr` actually points to `B`, this results in:\n\n❌ Undefined Behavior\n❌ No runtime type verification\n\n`static_cast` performs no dynamic check.\n\nOnly `dynamic_cast` verifies the real runtime type using RTTI.\n\n---\n\n## ⚠️ Where Undefined Behavior Appears\n\nUB can happen if:\n\n• You downcast using `static_cast` incorrectly\n• Base is not polymorphic (no virtual function)\n• You delete through a base pointer without virtual destructor\n\nThis exercise forces correct design to avoid UB.\n\n### 🛠 Viewing what the compiler generates (optional curiosity)\n\nTo see what happens under the hood, compile without optimizations:\n\n```bash\nc++ -std=c++98 -O0 -g3 *.cpp -o identify\n```\n\nThen inspect:\n\n```bash\nobjdump -d -C identify | less\n```\n\n* `-C` demangles C++ names\n* `-O0` prevents optimizations from hiding steps\n\nYou can also generate assembly directly:\n\n```bash\nc++ -std=c++98 -O0 -S main.cpp -o main.s\n```\n\n---\n\n## 🔍 Testing Tips\n\n### ex00\n\n```bash\n./convert a\n./convert 'a'\n./convert 0\n./convert 127\n./convert 128\n./convert -1\n./convert 2147483647\n./convert -2147483649\n./convert nan\n./convert +inf\n./convert -inff\n```\n\nThings to verify:\n\n* pseudo-literals output\n* overflow handling (`impossible`)\n* float rounding near `INT_MAX`\n* consistent `char` rules (ASCII safety)\n\n### ex01\n\n* check pointer equality after round-trip\n* print the `uintptr_t` value and compare with the original address formatting\n* ensure `Data` is non-empty\n\n### ex02\n\n* call `generate()` multiple times\n* test both `identify(ptr)` and `identify(ref)`\n* verify no `\u003ctypeinfo\u003e` usage\n\n---\n\n## 🧾 42 Notes\n\n* C++ modules do not use Norminette, but clean and readable code still matters.\n* During evaluation you’ll likely be asked to justify why you used a specific cast.\n* The “real learning” here is understanding precision, undefined behavior, and runtime type checks — not just passing the tests.\n\n---\n\nIf you’re a 42 student working on the same module: feel free to explore the code, get inspired, but **write your own implementation** — that’s where the learning happens. 🚀\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftigran-sargsyan-w%2Fcpp-module-06","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftigran-sargsyan-w%2Fcpp-module-06","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftigran-sargsyan-w%2Fcpp-module-06/lists"}