3.5 Type Conversion — Basic to User-Defined and Reverse
C++ supports rich type conversion mechanisms. We've seen implicit/explicit conversion between built-in types in Unit I. Here we focus on:
- Basic type → user-defined type (via constructor)
- User-defined type → basic type (via conversion operator)
- User-defined → user-defined
- Modern casts (
static_cast,dynamic_cast,const_cast,reinterpret_cast)
---
1. Basic Type → User-Defined (via Constructor)
A single-argument constructor doubles as a conversion function from its parameter type to the class type.
class Distance {
private:
int feet;
double inches;
public:
Distance(int totalInches) { // single-arg ctor
feet = totalInches / 12;
inches = totalInches % 12;
}
};
Distance d = 36; // 36 int → Distance(36) called → 3 feet 0 inches
This is an implicit conversion. To prevent it, mark the constructor explicit:
class Distance {
public:
explicit Distance(int totalInches) { /* ... */ }
};
Distance d = 36; // ERROR — needs explicit conversion
Distance d2(36); // OK — direct initialisation
Distance d3 = Distance(36); // OK — explicit cast
Modern advice: Mark all single-arg constructors explicit unless implicit conversion is desired. Prevents accidental conversions.
---
2. User-Defined → Basic Type (Conversion Operator)
To convert a class to a basic type, define a conversion operator (member function with special syntax):
class Distance {
private:
int feet;
double inches;
public:
Distance(int f, double i) : feet(f), inches(i) {}
operator double() const { // conversion to double — total inches
return feet * 12 + inches;
}
};
int main() {
Distance d(5, 6);
double inches = d; // calls operator double() — 66.0
cout << inches;
return 0;
}
Conversion operator syntax
operator <target-type>() const;
- No return type declared (target type is implicit)
- No parameters
- Usually
const(doesn't modify the object) - Should be explicit (C++11) to prevent surprises
explicit operator double() const { return feet * 12 + inches; }
// Now: double d = dist; // ERROR — needs explicit
// double d = static_cast<double>(dist); // OK
---
3. User-Defined → User-Defined
Conversion between two user-defined types can be done in two ways:
A. Constructor in the target class
class Inches {
public:
double value;
Inches(double v) : value(v) {}
};
class Centimeters {
public:
double value;
Centimeters(double v) : value(v) {}
Centimeters(const Inches& i) : value(i.value * 2.54) {} // Inches → Centimeters
};
Inches i(10);
Centimeters c = i; // calls Centimeters(Inches&) → 25.4
B. Conversion operator in the source class
class Inches {
public:
double value;
Inches(double v) : value(v) {}
operator Centimeters() const { // Inches → Centimeters
return Centimeters(value * 2.54);
}
};
Choosing between them
- Constructor approach: when target class is yours and source is foreign / library type
- Conversion operator approach: when source class is yours and target is foreign / library type
- Avoid defining both for the same pair — leads to ambiguity
---
Complete Example — Money Class
class Money {
private:
int rupees;
int paise;
public:
Money(int r = 0, int p = 0) : rupees(r), paise(p) {}
// Constructor from int (paise) — basic → user-defined
Money(int totalPaise) : rupees(totalPaise / 100), paise(totalPaise % 100) {}
// Conversion to double (rupees) — user-defined → basic
operator double() const {
return rupees + paise / 100.0;
}
void display() const {
cout << "₹" << rupees << "." << (paise < 10 ? "0" : "") << paise << endl;
}
};
int main() {
Money m1 = 10550; // int → Money(10550 paise) = ₹105.50
m1.display();
Money m2(75, 25); // ₹75.25
double d = m2; // Money → double = 75.25
cout << "As double: " << d << endl;
return 0;
}
---
Modern C++ Casts (recap from Unit I)
| Cast | Purpose | Example |
|---|---|---|
static_cast<T>(x) | Standard typed conversion | int i = static_cast<int>(3.14); |
dynamic_cast<T>(x) | Safe downcast in inheritance hierarchies | Derived d = dynamic_cast<Derived>(base); |
const_cast<T>(x) | Add/remove const | int p = const_cast<int>(constPtr); |
reinterpret_cast<T>(x) | Bit-level reinterpretation (unsafe) | int p = reinterpret_cast<int>(somePtr); |
dynamic_cast — RTTI in action
class Animal { virtual ~Animal() = default; };
class Dog : public Animal { public: void bark(); };
Animal* a = new Dog();
Dog* d = dynamic_cast<Dog*>(a); // safe downcast
if (d != nullptr) d->bark(); // safe — cast succeeded
Animal* a2 = new Animal();
Dog* d2 = dynamic_cast<Dog*>(a2); // returns nullptr — not a Dog
if (d2 == nullptr) cout << "Not a Dog\n";
dynamic_cast requires the base class to have at least one virtual function (it uses RTTI — Run-Time Type Information).
---
Implicit Conversion Sequence
When the compiler sees f(x), it tries to convert x to the parameter type using:
- Standard conversion —
int → double, etc. - User-defined conversion — constructor or operator
- Another standard conversion
Only one user-defined conversion is allowed in a sequence:
class A { public: A(int) {} };
class B { public: B(A) {} };
void f(B);
f(5); // ERROR — needs int → A → B (two user-defined conversions)
f(A(5)); // OK — explicit A construction; then A → B is one user-defined
---
Ambiguity Resolution in Conversion
If multiple conversion paths exist, the compiler may error with ambiguity:
class A {
public:
operator int() const { return 1; }
operator double() const { return 2.5; }
};
A a;
// long x = a; // ERROR — ambiguous (which conversion?)
long x = (int) a; // OK — explicit
---
Why explicit Matters
Without explicit, single-argument constructors enable silent conversions that can hide bugs:
class Date {
public:
Date(int days) { /* ... */ } // intended for clarity
};
void schedule(Date d);
schedule(5); // OK — compiler converts int 5 to Date(5)
// Was that what the caller meant? Probably not.
class Date {
public:
explicit Date(int days) { /* ... */ }
};
schedule(5); // ERROR — must explicitly convert
schedule(Date(5)); // OK — explicit
Modern C++ guideline: Mark all single-argument constructorsexplicitby default. Removeexplicitonly when implicit conversion is genuinely desired (rare — likestringfromconst char*).
---
Conversion Hierarchy
Conversion
│
┌───────────────┼───────────────┐
│ │ │
Standard User-Defined Identity
(int→double) │ (no conversion)
│
┌────────────┼────────────┐
│ │
Constructor Conversion
(basic → user) operator
(user → basic / user → user)
---
Type Conversion Comparison — exam table
| Direction | Mechanism | Example |
|---|---|---|
| Basic → Basic | Implicit / static_cast | double d = i; |
| Basic → User-defined | Single-arg constructor | Money m = 100; |
| User-defined → Basic | Conversion operator | double d = m; |
| User-defined → User-defined | Either (target ctor OR source operator) | Cm c = inches; |
| Base ↔ Derived (pointer/ref) | implicit (up), dynamic_cast (down) | Base* b = &derived; |
---
Putting it All Together — Temperature Class
class Celsius; // forward declaration
class Fahrenheit {
public:
double value;
explicit Fahrenheit(double v) : value(v) {}
operator Celsius() const; // declaration only
};
class Celsius {
public:
double value;
explicit Celsius(double v) : value(v) {}
// Constructor from Fahrenheit
explicit Celsius(const Fahrenheit& f) : value((f.value - 32) * 5.0 / 9.0) {}
// Conversion operator to Fahrenheit
operator Fahrenheit() const {
return Fahrenheit(value * 9.0 / 5.0 + 32);
}
};
Fahrenheit::operator Celsius() const {
return Celsius((value - 32) * 5.0 / 9.0);
}
int main() {
Fahrenheit f(98.6);
Celsius c = static_cast<Celsius>(f); // explicit conversion
cout << "98.6 F = " << c.value << " C" << endl; // 37
Fahrenheit f2 = static_cast<Fahrenheit>(c);
cout << c.value << " C = " << f2.value << " F" << endl;
return 0;
}
---
Study deep
explicitis the safer default. Implicit conversions are convenient but enable silent bugs. Mark constructors and conversion operatorsexplicitunless you've thought hard about the use case.
- Conversion operators are dangerous. They can interact unexpectedly with overload resolution. Use sparingly; document why each is needed.
- C++ has more conversions than most languages. Java has explicit casts only for narrowing; C# similar; Python via constructors. C++'s combination of implicit conversion + operator overloading + constructor conversion is extremely flexible — and extremely easy to misuse.
dynamic_castis slower thanstatic_cast. It involves RTTI lookup. Use only when you genuinely need runtime type checking; prefer virtual functions for polymorphic behaviour.
- C++20
explicit(condition). You can makeexplicitconditional based on a compile-time boolean — useful in templates:
template<typename T>
class Wrapper {
public:
explicit(!std::is_constructible_v<int, T>) Wrapper(T) { /* ... */ }
};
Key Terms — Lesson 3.5
The terms below cover the three directions of type conversion and the C++ casts that perform them.
Type Conversion / Type Cast — Converting a value from one type to another. Conversions are implicit (automatic when the compiler can) or explicit (caller requests with a cast). Conversions can be lossless (int → double) or lossy (double → int, dropping fractional part).
Implicit Conversion — Conversion the compiler performs automatically when one type is assignable to another and the language defines the conversion. int → double is implicit; double → int is implicit (but loses fractional part); user-defined classes can also participate via non-explicit constructors and conversion operators.
Explicit Conversion — A conversion the developer requests explicitly through a cast — static_cast<int>(d), (int)d, or a constructor call int(d). Required when the compiler refuses implicit conversion or when the developer wants to signal intent.
Basic-to-User-Defined Conversion — Converting a built-in type to a user-defined class via a single-argument constructor. Distance d = 5; calls Distance(int) if it exists and is not explicit. The most common form of user-defined conversion.
User-Defined-to-Basic Conversion — Converting a class instance to a built-in type via a conversion operator. operator int() const; lets the class be used in places expecting an int. int n = myObj; calls the conversion operator.
User-Defined-to-User-Defined Conversion — Converting between two different user-defined classes. Can be done via a constructor in the destination class (Celsius(const Fahrenheit&)) or a conversion operator in the source class (Fahrenheit::operator Celsius()).
Conversion Constructor — A constructor that takes a single argument of one type and creates an object of the class. Without explicit, the compiler may call it implicitly for conversions — sometimes useful, often surprising.
Conversion Operator — A special member function operator TypeName() const that converts the class instance to TypeName. operator int() const { return value; } lets a class behave like an int in some contexts.
explicit Constructor / Operator (C++11) — A constructor or conversion operator declared with explicit disables implicit invocation. explicit Distance(int); blocks Distance d = 5; but allows Distance d(5); or static_cast<Distance>(5). The safer default; widely recommended for any single-argument constructor where conversion isn't genuinely desired.
Narrowing Conversion — A conversion that may lose information — double → int (loses fraction), long → short (may lose magnitude). Many narrowing conversions are implicit (silently lossy); brace initialisation {} rejects narrowing at compile time.
Widening / Promoting Conversion — A conversion that does not lose information — int → long, float → double, derived → base. Always safe; always implicit.
C-Style Cast (T)x — The legacy cast inherited from C. Will perform whichever conversion is needed — static, const, reinterpret — without distinguishing. Powerful but dangerous; modern C++ deprecates it in favour of the four named casts.
static_cast<T>(expr) — The standard C++ cast for well-defined conversions — int to double, base pointer to derived pointer when known safe, void* to typed pointer. Checked at compile time; rejects unrelated types.
dynamic_cast<T>(expr) — A C++ cast for safe downcasting in a polymorphic hierarchy. Returns a valid derived pointer if the runtime type matches, nullptr if not. Uses RTTI; requires at least one virtual function in the base. Slower than static_cast.
const_cast<T>(expr) — A cast that adds or removes const-ness from a pointer or reference. Use sparingly — usually a sign that the design has a const-correctness problem. Modifying an object through a const_cast-removed pointer is undefined behaviour if the object was originally const.
reinterpret_cast<T>(expr) — A cast that reinterprets the raw bit pattern of an object as another type — pointer to int, pointer to unrelated pointer type. The most unsafe cast; only justified in low-level code (serialisation, hardware interfaces, network buffers).
Static vs Dynamic Cast Comparison — static_cast is compile-time — uses static type information; faster; cannot detect mismatched runtime types. dynamic_cast is runtime — uses RTTI; can verify the actual type; returns null if mismatched. Use static_cast when you're certain; use dynamic_cast when you need to check.
RTTI (Run-Time Type Information) — Compiler-generated metadata that lets a program inspect the actual type of an object at runtime. Required for dynamic_cast and typeid. RTTI is on by default; some embedded codebases disable it (-fno-rtti) to reduce code size.
Conversion Sequence / Best Match — When several conversions could apply, the compiler ranks them and picks the best match. The ranking is standard conversion > user-defined conversion > ellipsis conversion. Ambiguous cases (two equally-good matches) cause compile errors.
explicit(condition) (C++20) — A conditional version of explicit whose effect depends on a compile-time Boolean. Useful in templates where you want explicit-ness only for certain instantiations.
Implicit Conversion Pitfalls — Implicit conversions are convenient but enable silent bugs — bool ↔ int conversions, int ↔ enum, single-argument constructor used unexpectedly. Modern style: mark conversion constructors explicit unless implicit conversion is the design intent.
Spaceship Operator and Three-Way Comparison (C++20) — Related to type conversion in that comparison sometimes implies conversion. The <=> operator's auto-generated comparisons follow the same rules as explicit <, ==, etc., including any conversion rules in scope.
---
PYQ pattern: "Explain type conversion between basic types and user-defined types in C++." — Three directions (basic → user via ctor; user → basic via operator; user → user); show examples; mention explicit keyword.
PYQ pattern: "Write a C++ program to convert Celsius to Fahrenheit using conversion functions." — Use class with constructor + conversion operator; demonstrate both directions.
PYQ pattern: "Differentiate the four C++ casts (static, dynamic, const, reinterpret)." — Brief role of each;static_cast(standard),dynamic_cast(RTTI for inheritance),const_cast(const removal),reinterpret_cast(low-level/dangerous).