Project 3: Compile-Time Unit Conversion Library
Encode physical units in the type system so invalid math fails at compile time.
Quick Reference
| Attribute | Value |
|---|---|
| Difficulty | Expert |
| Time Estimate | 2-3 weeks |
| Main Programming Language | C++17 |
| Coolness Level | Pure Magic |
| Business Potential | Resume Gold |
| Prerequisites | Templates, type traits, static_assert |
| Key Topics | TMP, std::ratio, dimensional analysis |
1. Learning Objectives
- Represent dimensions at compile time.
- Use template metaprogramming to compute unit arithmetic.
- Provide clear compile-time errors for invalid operations.
- Build a zero-runtime-cost safety layer.
2. All Theory Needed (Per-Concept Breakdown)
2.1 Template Metaprogramming Basics
Description
TMP lets you compute types and constants during compilation. Your unit system is purely compile-time.
Definitions & Key Terms
- Instantiation: compiler generates code from templates.
- Type trait: compile-time query.
Mental Model Diagram (ASCII)
Template + Types -> Compiler -> Generated Types
How It Works
- Templates are expanded during compile time.
- Errors show up as type mismatches.
Minimal Concrete Example
template<typename T> struct is_length : std::false_type {};
Common Misconceptions
- “TMP is too slow” -> correct design keeps compile time manageable.
Check-Your-Understanding Questions
- Why is SFINAE useful for constraints?
Where You’ll Apply It
- All operators in this project
2.2 Dimensional Analysis
Description
Physical equations must be dimensionally consistent. Encoding dimensions in types enforces that rule.
Definitions & Key Terms
- Base dimension: mass, length, time, etc.
- Exponent vector: type-level exponents for each base.
Mental Model Diagram (ASCII)
Meters: (0,1,0)
Seconds: (0,0,1)
Velocity: (0,1,-1)
Minimal Concrete Example
template<int M, int L, int T> struct Dim {};
Common Misconceptions
- “Units only matter at runtime” -> they can be enforced by the compiler.
Where You’ll Apply It
- Unit multiplication/division
2.3 std::ratio and Scaling
Description
std::ratio encodes rational numbers at compile time and lets you express prefixes like kilo or milli.
Definitions & Key Terms
- Ratio: compile-time fraction (
1/1000).
Mental Model Diagram (ASCII)
km = ratio<1000,1>
mm = ratio<1,1000>
Minimal Concrete Example
using kilo = std::ratio<1000,1>;
Common Misconceptions
- “Ratios cost runtime” -> they are compile-time constants.
3. Project Specification
3.1 What You Will Build
A small unit library that supports base units, derived units, scaling factors, and operator overloads with compile-time validation.
3.2 Functional Requirements
- Base dimensions and derived units.
- Operator
+only for identical units. - Operator
*and/create new dimensions. - Scaling with
std::ratio(kilo, milli).
3.3 Non-Functional Requirements
- Zero runtime overhead for unit checks.
- Clear compiler errors.
3.4 Example Usage / Output
Meters d(10.0);
Seconds t(2.0);
auto v = d / t; // Velocity
std::cout << v.count() << " m/s\n";
3.5 Data Formats / Schemas / Protocols
- Type-level dimension struct:
Dim<M,L,T> - Value wrapper:
Value<Dim, Ratio>
3.6 Edge Cases
- Adding incompatible units
- Dividing by zero (runtime)
3.7 Real World Outcome
3.7.1 How to Run
cmake -S . -B build
cmake --build build
./build/units_demo
3.7.2 Golden Path Demo
10 m/s
3.7.3 Failure Demo
error: static assertion failed: Cannot add values with different dimensions
4. Solution Architecture
4.1 High-Level Design
[Dim<M,L,T>] + [Ratio] -> Value<Dim, Ratio>
4.2 Key Components
| Component | Responsibility | Key Decisions | |—|—|—| | Dim | exponent vector | fixed ordering of bases | | Value | runtime value + unit | strong type safety | | Operators | dimension arithmetic | compile-time checks |
4.3 Data Structures
template<typename Dim, typename Ratio = std::ratio<1>>
struct Value { double v; };
4.4 Algorithm Overview
- Multiply: add exponents
- Divide: subtract exponents
5. Implementation Guide
5.1 Development Environment Setup
cmake -S . -B build
cmake --build build
5.2 Project Structure
units/
├── src/units.hpp
├── src/units.cpp
├── tests/units_tests.cpp
└── CMakeLists.txt
5.3 The Core Question You’re Answering
“Can the compiler prevent incorrect equations before the program runs?”
5.4 Concepts You Must Understand First
- TMP and SFINAE
std::ratiostatic_assert
5.5 Questions to Guide Your Design
- How will you encode base units?
- Where will you perform exponent arithmetic?
- How will you display units in output?
5.6 Thinking Exercise
Compute the dimension vector for acceleration and force.
5.7 The Interview Questions They’ll Ask
- What is SFINAE?
- How does
static_asserthelp API design? - Why are zero-cost abstractions important?
5.8 Hints in Layers
Hint 1: Represent dimensions as int template parameters.
Hint 2: Use helper templates for dimension arithmetic.
Hint 3: Use static_assert for friendly errors.
5.9 Books That Will Help
| Topic | Book | Chapter | |—|—|—| | TMP | C++ Templates | TMP chapters | | Type system | The C++ Programming Language | Templates chapter | | Generics | A Tour of C++ | Generics section |
5.10 Implementation Phases
Phase 1: Base Units (3-4 days)
- Define
DimandValue. - Checkpoint: compile and run simple units.
Phase 2: Operators (4-5 days)
- Implement
+,-,*,/. - Checkpoint: invalid adds fail.
Phase 3: Scaling and Polishing (3-4 days)
- Add
std::ratioscaling. - Checkpoint: km to m conversions.
5.11 Key Implementation Decisions
| Decision | Options | Recommendation | Rationale | |—|—|—|—| | Dimension encoding | int pack | int pack | simple, fast compile | | Error handling | SFINAE / static_assert | static_assert | clearer messages |
6. Testing Strategy
6.1 Test Categories
| Category | Purpose | Examples | |—|—|—| | Compile-time | invalid ops | meters + seconds fails | | Runtime | numeric output | velocity calc |
6.2 Critical Test Cases
meters + secondsfails to compile.meters / secondsyields velocity.
7. Common Pitfalls & Debugging
7.1 Frequent Mistakes
| Pitfall | Symptom | Solution |
|—|—|—|
| Bad error messages | huge template dump | use static_assert |
| Wrong exponent math | incorrect derived units | unit tests |
8. Extensions & Challenges
8.1 Beginner Extensions
- Add more base dimensions (current, temperature).
8.2 Intermediate Extensions
- Add user-defined literals (
10.0_m).
8.3 Advanced Extensions
- Integrate with
std::chronoor Boost.Units.
9. Real-World Connections
9.1 Industry Applications
- Physics engines
- Scientific computing
10. Resources
10.1 Essential Reading
- C++ Templates (TMP chapters)
- The C++ Programming Language (templates)
11. Self-Assessment Checklist
- I can encode base units as types.
- I can explain why invalid units fail to compile.
12. Submission / Completion Criteria
Minimum Viable Completion:
- Base units + operator checks
Full Completion:
- Scaling support with
std::ratio
Excellence:
- User-defined literals and derived-unit aliases