Programming Language & Its Applications — Features of Object-Oriented Programming, NEC licence examination syllabus (Nepal Engineering Council).
Converting between your class and other types — via constructors going in, and conversion operators going out.
std::string s = "hello"; works even though "hello" is a const char*, and why if (myFileStream) works even though a stream is not a bool. They are also the source of some of C++'s nastiest surprises — a single unmarked constructor can make myVector = 10; compile and do something you never intended. This is exactly why the explicit keyword exists, and why modern code sprinkles it liberally. Search "explicit constructor C++ why" and then "safe bool idiom" to see how much design effort went into making if (stream) work without also making stream + 1 compile.#include <iostream>
class Celsius; /* forward declaration */
class Fahrenheit {
double deg;
public:
/* 1. double -> Fahrenheit (converting constructor) */
Fahrenheit(double d = 0) : deg(d) {}
/* 2. Fahrenheit -> double (conversion operator) */
operator double() const { return deg; }
double raw() const { return deg; }
};
class Celsius {
double deg;
public:
Celsius(double d = 0) : deg(d) {}
/* 3. Celsius -> Fahrenheit, via an operator in the
SOURCE class. Note: Fahrenheit deliberately has
NO Celsius constructor, or this would be
ambiguous. */
operator Fahrenheit() const {
return Fahrenheit(deg * 9.0/5.0 + 32);
}
double raw() const { return deg; }
};
int main() {
/* basic -> class */
Fahrenheit f = 98.6;
std::cout << "Fahrenheit from 98.6 = " << f.raw() << "\n";
/* class -> basic */
double d = f;
std::cout << "back to double = " << d << "\n";
/* class -> class */
Celsius c(37);
Fahrenheit f2 = c;
std::cout << "37 C as Fahrenheit = " << f2.raw() << "\n";
/* the conversion operator also enables arithmetic —
f becomes a double, then ordinary maths applies */
std::cout << "f + 1.4 = " << f + 1.4 << "\n";
std::cout << "f > 100 ? = " << std::boolalpha
<< (f > 100) << "\n";
Celsius boiling(100);
Fahrenheit fb = boiling;
std::cout << "100 C as Fahrenheit = " << fb.raw() << "\n";
return 0;
}
Output:
Fahrenheit from 98.6 = 98.6
back to double = 98.6
37 C as Fahrenheit = 98.6
f + 1.4 = 100
f > 100 ? = false
100 C as Fahrenheit = 212
explicit exists#include <iostream>
class Loose {
int n;
public:
Loose(int v) : n(v) {} /* implicit */
operator int() const { return n; } /* implicit */
int get() const { return n; }
};
class Strict {
int n;
public:
explicit Strict(int v) : n(v) {}
explicit operator int() const { return n; }
int get() const { return n; }
};
void takesLoose(Loose l) { std::cout << " Loose(" << l.get() << ")\n"; }
void takesStrict(Strict s){ std::cout << " Strict(" << s.get() << ")\n"; }
int main() {
/* Loose accepts anything int-ish, silently */
Loose a = 5; takesLoose(7);
Loose b = 'A'; /* char -> int -> Loose */
Loose c = true; /* bool -> int -> Loose */
std::cout << " b from 'A' = " << b.get()
<< ", c from true = " << c.get() << "\n";
/* and it silently decays back to int, so this compiles: */
Loose x(10), y(3);
std::cout << " x / y as ints = " << x / y << "\n";
std::cout << " x << 2 = " << (x << 2) << "\n";
/* bit-shifting a Loose is meaningless, yet legal */
/* Strict refuses all of that */
Strict s(5); /* ✔ direct init */
/* Strict t = 5; ✗ error: conversion from int
to Strict is explicit */
/* takesStrict(7); ✗ error */
/* int v = s; ✗ error */
int v = static_cast<int>(s); /* ✔ deliberate */
std::cout << " Strict via static_cast = " << v << "\n";
return 0;
}
Output:
Loose(7)
b from 'A' = 65, c from true = 1
x / y as ints = 3
x << 2 = 40
Strict via static_cast = 20
A→B and B→C, the compiler will not silently do A→C. That single rule is what stops implicit conversions from becoming completely unpredictable — and it is why sometimes a conversion you expect does not happen, which can be equally confusing.
#include <iostream>
class Metre;
class Foot {
double v;
public:
explicit Foot(double x = 0) : v(x) {}
double value() const { return v; }
/* conversion to Metre declared here, defined after
Metre is complete */
operator Metre() const;
};
class Metre {
double v;
public:
explicit Metre(double x = 0) : v(x) {}
double value() const { return v; }
/* NOTE: no Foot constructor here. Exactly one path
exists (Foot::operator Metre), so no ambiguity. */
/* arithmetic stays inside the type — dimensionally safe */
friend Metre operator+(const Metre &a, const Metre &b) {
return Metre(a.v + b.v);
}
friend bool operator<(const Metre &a, const Metre &b) {
return a.v < b.v;
}
friend std::ostream& operator<<(std::ostream &os,
const Metre &m) {
return os << m.v << " m";
}
};
Foot::operator Metre() const { return Metre(v * 0.3048); }
int main() {
Metre a(100);
Foot b(328.084);
/* Foot converts to Metre, then Metre + Metre applies */
Metre total = a + b;
std::cout << "100 m + 328.084 ft = " << total << "\n";
Metre c = b;
std::cout << "328.084 ft = " << c << "\n";
std::cout << std::boolalpha
<< "is 100 m < 328.084 ft? " << (a < c) << "\n";
/* explicit ctor blocks the nonsense case: */
/* Metre wrong = 50; error — 50 what? metres? feet?
The explicit keyword forces you
to say Metre(50). */
Metre right(50);
std::cout << "explicit is required: " << right << "\n";
return 0;
}
Output:
100 m + 328.084 ft = 200 m
328.084 ft = 99.9999 m
is 100 m < 328.084 ft? false
explicit is required: 50 m
Foot to Metre without an explicit conversion would have caught it at compile time. C++ now has std::chrono as a working example (you cannot accidentally add seconds to milliseconds — it converts correctly), and C++26 is adding a full units library. Search "Mars Climate Orbiter unit conversion" and "std::chrono duration cast"; the second shows what the fix looks like in real code.explicit is high-value: state that it blocks implicit conversion while still allowing direct initialisation and static_cast. Mention that only one user-defined conversion is applied per implicit chain.Create a free account to tick topics off, take notes as you read, watch the video lessons and get a day-by-day study plan built around your exam date.
Loading…