1 Implicit Conversion
Implicit constructor (automatic conversions that the compiler performs without us having to cast them) conversion refers to the automatic conversion of one data type to another through the use of constructors, particularly when the constructor can be called with a single argument. This mechanism is inherent in C++ and can lead to implicit type conversions, where a value of one type is automatically converted to another type without explicit instructions from the programmer.
#1.1 Single-Argument Constructors
In C++, constructors with a single parameter can act as implicit conversion functions. When an object of a class is being initialized with a value that matches the type of the constructor parameter, the constructor is invoked implicitly to perform the conversion.
class Distance {
private:
int meters;
public:
Distance(int m) : meters(m) {}
// Other members...
};
int main() {
Distance d = 1000; // Implicit conversion from int to Distance
return 0;
}
In this example, the integer 1000
is implicitly converted to a Distance
object using the constructor Distance(int m)
.
class Temperature {
private:
double celsius;
public:
Temperature(double c) : celsius(c) {}
// Other members...
};
int main() {
Temperature t = 25.5; // Implicit conversion from double to Temperature
return 0;
}
Here, the double value 25.5
is automatically converted to a Temperature
object using the constructor Temperature(double c)
.
#1.2 Conversion Operator
A conversion operator is a special type of member function of a class that specifies how objects of that class can be converted to another type. It has the following syntax:
operator target_type() const;
Here, operator
is a keyword, target_type
is the type to which the conversion is performed, and const
specifies that the operator does not modify the object it operates on. The return type of the conversion operator is implicitly defined by the target_type
.
#include <iostream>
class Distance {
private:
int meters;
public:
Distance(int m) : meters(m) {}
// Conversion operator to convert Distance to int
operator int() const {
return meters;
}
};
int main() {
Distance d(100);
int meters = d; // Implicit conversion using conversion operator
std::cout << "Distance in meters: " << meters << std::endl;
return 0;
}
In this example, the conversion operator operator int()
allows objects of the Distance
class to be implicitly converted to int
. When Distance
objects are assigned to an int
variable or used in contexts where an int
is expected, the conversion operator is automatically invoked.
2 The Problem
Suppose we have a class MyClass
with a constructor that takes an integer argument:
#include <iostream>
class MyClass {
public:
MyClass(int arg) {
value = arg;
}
int getValue() {
return value;
}
private:
int value;
};
void func(MyClass obj) {
std::cout << "Value: " << obj.getValue() << std::endl;
}
int main() {
MyClass obj1 = 10; // Implicit conversion, potential issue
MyClass obj2(20); // Explicit conversion, no problem
func(obj2); // Passing object of MyClass, no issue
return 0;
}
In the main
function, obj1
is initialized with an integer 10
. However, because the constructor of MyClass
is not marked as explicit
, the compiler automatically performs an implicit conversion from int
to MyClass
. This implicit conversion might seem innocent at first glance, but it can introduce subtle bugs and make the code less predictable.
3 The Solution : “explicit
” Keyword
In C++, the explicit keyword is used to qualify constructors and conversion functions, indicating that they should not participate in implicit type conversions. It restricts the compiler from automatically invoking these functions for implicit conversions, promoting clarity and preventing potential ambiguities in the code.
Explicit Keyword used to mark constructors to not implicitly convert types. It is optional for constructors that take exactly one argument and work on constructors(with a single argument) since those are the only constructors that can be used in typecasting.
The explicit keyword disallows “implicit conversion” from single arguments or braced initializers. Whereas a non-explicit constructor enables implicit conversion.
#3.1 The explicit keyword
we can use the explicit keyword to tell the compiler that a constructor should not be used as a converting constructor.
Making a constructor explicit has two notable consequences:
- An explicit constructor cannot be used to do copy initialization or copy list initialization.
- An explicit constructor cannot be used to do implicit conversions (since this uses copy initialization or copy list initialization).
Example:
#include <iostream>
class MyClass {
public:
explicit MyClass(int arg) {
value = arg;
}
int getValue() {
return value;
}
private:
int value;
};
void func(MyClass obj) {
std::cout << "Value: " << obj.getValue() << std::endl;
}
int main() {
MyClass obj1 = 10; // Error: Implicit conversion not allowed
MyClass obj2(20); // OK: Explicit conversion
func(obj2); // OK: Passing object of MyClass
return 0;
}
By marking the constructor of MyClass
as explicit
, any attempt to implicitly convert an integer to a MyClass
object will result in a compilation error. This forces the developer to explicitly specify the type conversion, making the code more readable and preventing potential bugs.
struct A
{
A(int) { }
operator bool() const { return true; }
};
struct B
{
explicit B(int) {}
explicit operator bool() const { return true; }
};
void doA(A a) {}
void doB(B b) {}
int main()
{
A a1(1); // OK:direct initialization
A a2 = 1; // OK:copy initialization
A a3{ 1 }; // OK:direct list initialization
A a4 = { 1 }; // OK:copy list initialization
A a5 = (A)1; // OK:Allow explicit conversion of static_cast
doA(1); // OK:Allow implicit conversion from int to A
if (a1); // OK: implicit conversion from A to bool using conversion function A ::operator bool()
bool a6(a1); // OK: implicit conversion from A to bool using conversion function A::operator bool()
bool a7 = a1; // OK: implicit conversion from A to bool using conversion function A::operator bool()
bool a8 = static_cast<bool>(a1); // OK: static_cast for direct initialization
B b1(1); // OK:direct initialization
B b2 = 1; // Error: Object modified by explicit constructor cannot be initialized by copying
B b3{ 1 }; // OK:direct list initialization
B b4 = { 1 }; // Error: Object modified by explicit constructor cannot copy list initialization
B b5 = (B)1; // OK: Allow explicit conversion of static_cast
doB(1); // Error: Objects whose constructor is explicitly modified cannot be implicitly converted from int to B
if (b1); // OK: objects modified by explicit conversion function B::operator bool() can be converted from B to bool by context
bool b6(b1); // OK: Explicitly modified conversion function B::operator The object of bool() can be converted from B to bool by context
bool b7 = b1; // Error: Objects modified by explicit conversion function B :: operator bool () cannot be implicitly converted
bool b8 = static_cast<bool>(b1); // OK: static_cast performs direct initialization
return 0;
}
4 When to Use explicit
While explicit can be immensely useful, it's essential to apply it judiciously. Here are some guidelines:
- Single-Argument Constructors: Apply explicit to single-argument constructors that aren't meant for implicit conversions.
- Conversion Operators: Use explicit with conversion operators to prevent implicit conversions between types.
- Consistency: Consider the overall design and maintain consistency within your codebase. Applying explicit uniformly across similar scenarios can improve code clarity.