Siksha Sarovar

Siksha Sarovar (sikshasarovar.com) is a free educational web application that helps students in India learn programming and prepare for academic and competitive exams. The platform offers structured coding courses (C, C++, Python, Java, HTML, CSS, PHP, Power BI, AI, Machine Learning, Data Science), complete university curriculum notes for BCA/MCA students with previous year question papers, Class 10 and Class 12 CBSE/HBSE school notes, and dedicated preparation material for SSC, UPSC, Banking, Railway and other government exams. Browsing the site is completely free and requires no account. Users may optionally sign in with Google solely to save their learning progress, quiz scores and personal preferences across devices.

Privacy Policy | Terms of Service | Contact Siksha Sarovar | About Siksha Sarovar

v4.0.9 · PWA
Siksha Sarovar logo
Siksha Sarovar
Your Learning Universe

Siksha Sarovar is a free e-learning platform for coding courses, BCA university notes and competitive exam preparation. Optional Google sign-in saves your learning progress across devices.

Initializing knowledge base…
Compiling modules 0%

3.5 Type Conversion — Basic to User-Defined and Reverse

Lesson 17 of 22 in the free Object Oriented Programming with C++ notes on Siksha Sarovar, written by Rohit Jangra.

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:

  1. Basic type → user-defined type (via constructor)
  2. User-defined type → basic type (via conversion operator)
  3. User-defined → user-defined
  4. 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)

CastPurposeExample
static_cast<T>(x)Standard typed conversionint i = static_cast<int>(3.14);
dynamic_cast<T>(x)Safe downcast in inheritance hierarchiesDerived d = dynamic_cast<Derived>(base);
const_cast<T>(x)Add/remove constint 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:

  1. Standard conversionint → double, etc.
  2. User-defined conversion — constructor or operator
  3. 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 constructors explicit by default. Remove explicit only when implicit conversion is genuinely desired (rare — like string from const 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

DirectionMechanismExample
Basic → BasicImplicit / static_castdouble d = i;
Basic → User-definedSingle-arg constructorMoney m = 100;
User-defined → BasicConversion operatordouble d = m;
User-defined → User-definedEither (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

  1. explicit is the safer default. Implicit conversions are convenient but enable silent bugs. Mark constructors and conversion operators explicit unless you've thought hard about the use case.
  1. Conversion operators are dangerous. They can interact unexpectedly with overload resolution. Use sparingly; document why each is needed.
  1. 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.
  1. dynamic_cast is slower than static_cast. It involves RTTI lookup. Use only when you genuinely need runtime type checking; prefer virtual functions for polymorphic behaviour.
  1. C++20 explicit(condition). You can make explicit conditional 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 (intdouble) or lossy (doubleint, dropping fractional part).

Implicit Conversion — Conversion the compiler performs automatically when one type is assignable to another and the language defines the conversion. intdouble is implicit; doubleint 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 informationdoubleint (loses fraction), longshort (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 informationintlong, floatdouble, 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 Comparisonstatic_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 — boolint 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).