Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Preventing Implicit Conversions
Prevent surprising implicit conversions by marking single-argument constructors explicit.
Converting Constructors and the Explicit Keyword
Constructors that can be called with a single argument enable implicit conversions. While convenient, this can lead to unexpected behavior.
Converting constructors
A converting constructor is any constructor that can be called with one argument:
class Distance
{
public:
double meters{};
Distance(double m) // Converting constructor
: meters{ m }
{
}
};
void printDistance(Distance d)
{
std::cout << d.meters << "m\n";
}
int main()
{
printDistance(10.5); // Implicitly converts double to Distance!
return 0;
}
The compiler automatically creates a Distance object from the double value.
When implicit conversion happens
Function arguments:
void process(Distance d);
process(5.0); // Implicitly converts
Assignment:
Distance d{};
d = 25.0; // Implicitly converts double to Distance, then assigns
Return values:
Distance getDistance()
{
return 100.0; // Implicitly converts
}
Comparisons and operations:
Distance d1{50.0};
if (d1 == 50.0) // Converts 50.0 to Distance, then compares
std::cout << "Match\n";
The problem with implicit conversion
Implicit conversions can hide bugs:
class Minutes
{
public:
int value{};
Minutes(int m) : value{ m } {}
};
void scheduleTask(Minutes duration)
{
std::cout << "Task scheduled for " << duration.value << " minutes\n";
}
int main()
{
scheduleTask(60); // Intended: 60 minutes
scheduleTask(1.5); // Bug! Converts 1.5 to int (1), then to Minutes
// Silently truncates to 1 minute!
return 0;
}
The second call compiles but behaves unexpectedly.
The explicit keyword
Mark constructors explicit to prevent implicit conversions:
class Minutes
{
public:
int value{};
explicit Minutes(int m) : value{ m } {}
};
void scheduleTask(Minutes duration)
{
std::cout << "Task scheduled for " << duration.value << " minutes\n";
}
int main()
{
scheduleTask(Minutes{60}); // OK: explicit construction
scheduleTask(60); // Error: implicit conversion prevented
return 0;
}
Now you must explicitly construct Minutes objects, making intent clear and preventing bugs.
When to use explicit
Use explicit for:
- Constructors taking a single argument
- Constructors with defaulted parameters that could be called with one argument
Example needing explicit:
class Age
{
public:
int years{};
explicit Age(int y) : years{ y } {}
};
void printAge(Age a)
{
std::cout << a.years << " years\n";
}
int main()
{
printAge(Age{25}); // OK: explicit construction
printAge(25); // Error: prevented implicit conversion
return 0;
}
Example where implicit might be fine:
class String
{
private:
char* data{};
public:
String(const char* str) // Could be non-explicit for convenience
{
// ... copy str to data ...
}
};
void display(String s);
display("Hello"); // Convenient: implicit conversion from const char*
Standard library types like std::string allow this for convenience. It's a design choice.
Multiple arguments and explicit
Constructors with multiple arguments don't cause implicit conversion in function calls:
class Point
{
public:
int x{}, y{};
Point(int xPos, int yPos) : x{ xPos }, y{ yPos } {}
};
void drawPoint(Point p);
drawPoint(5, 10); // Error: even without explicit
drawPoint(Point{5, 10}); // Must explicitly construct
But they can still cause issues with initialization:
Point p1 = {5, 10}; // List initialization, works
Use explicit if you want to prevent even this:
class Point
{
public:
int x{}, y{};
explicit Point(int xPos, int yPos) : x{ xPos }, y{ yPos } {}
};
Point p1{5, 10}; // OK: direct initialization
Point p2 = {5, 10}; // Error: copy-list-initialization prevented
explicit with default arguments
A constructor with default arguments can act as a single-argument constructor:
class Config
{
public:
int setting1{};
int setting2{};
explicit Config(int s1, int s2 = 0) // Can be called with one argument
: setting1{ s1 }, setting2{ s2 }
{
}
};
void apply(Config c);
apply(5); // Error: explicit prevents this
apply(Config{5}); // OK
Real-world example
class Temperature
{
private:
double kelvin{};
public:
explicit Temperature(double k) : kelvin{ k }
{
if (k < 0)
kelvin = 0; // Can't be negative
}
double getKelvin() const { return kelvin; }
};
void setRoomTemp(Temperature t)
{
std::cout << "Setting to " << t.getKelvin() << "K\n";
}
int main()
{
setRoomTemp(Temperature{293.15}); // OK: 20°C in Kelvin
setRoomTemp(293.15); // Error: must be explicit
// This prevents accidentally passing Celsius when Kelvin expected
return 0;
}
Default to explicit by marking single-argument constructors explicit unless there's a good reason not to. Consider allowing implicit conversion only for wrapper types where conversion is natural (like std::string from const char*), numeric types where conversion is unambiguous, or types designed specifically for implicit conversion. Be consistent and document if you allow implicit conversion. Test that both implicit and explicit construction work as expected.
class Wrapper
{
public:
int value{};
// Explicit by default for safety
explicit Wrapper(int v) : value{ v } {}
};
Summary
Converting constructors: Constructors that can be called with a single argument enable implicit type conversions, allowing the compiler to automatically construct objects from other types.
When implicit conversion happens: Implicit conversions occur during function calls, assignments, return values, comparisons, and other contexts where a different type is expected.
The problem: Implicit conversions can hide bugs by silently converting types in unexpected ways, such as truncating floating-point values to integers or creating temporary objects where they weren't intended.
The explicit keyword: Marking constructors explicit prevents implicit conversions, requiring users to explicitly construct objects and making their intent clear in code.
When to use explicit: Use explicit for single-argument constructors and constructors with default parameters that could be called with one argument to prevent accidental conversions.
Multiple arguments: Constructors with multiple required arguments don't cause implicit conversion in function calls, but can still cause issues with list initialization unless marked explicit.
Default arguments: Constructors with default arguments can act as single-argument constructors, so mark them explicit to prevent implicit conversions.
The explicit keyword is a safety feature that prevents subtle bugs caused by unexpected implicit conversions. By defaulting to explicit for single-argument constructors, you create more predictable, maintainable code where type conversions are intentional and visible.
Preventing Implicit Conversions - Quiz
Test your understanding of the lesson.
Practice Exercises
Explicit Constructors
Use the explicit keyword to prevent unwanted implicit conversions.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!