C++ Conversion Constructors: Implicit vs Explicit
In C++, a conversion constructor allows the compiler to transform a
value of a source type into an object of a target class. By default, any
constructor that can be called with a single argument acts as an
implicit conversion constructor, allowing the compiler to perform
automatic type conversions behind the scenes. However, developers can
prepend the explicit keyword to these constructors to
disable automatic conversions, requiring callers to explicitly cast or
construct the type. Understanding how implicit and explicit conversions
operate is essential for writing safe, predictable, and maintainable C++
code.
What Is a Conversion Constructor?
A conversion constructor is any constructor that can be invoked with a single argument. It defines a conversion path from the parameter's type (the source type) to the class type being defined (the target type). This includes:
- Constructors with a single parameter.
- Constructors with multiple parameters where every parameter after the first has a default argument.
class Distance {
public:
// Conversion constructor from meters (double) to Distance
Distance(double meters) : meters_(meters) {}
private:
double meters_;
};Implicit Conversions
Unless declared with the explicit specifier,
single-argument constructors participate in implicit conversions. When
the compiler encounters an expression expecting a class type, but
receives a value of the source type, it automatically constructs a
temporary object using the conversion constructor.
Implicit conversion can occur in several contexts:
- Copy Initialization: Assigning a source type directly to a class instance.
- Function Arguments: Passing a source type to a function expecting the class type.
- Return Values: Returning a source type from a function declared to return the class type.
void PrintDistance(Distance d);
int main() {
// 1. Copy initialization
Distance d1 = 5.0;
// 2. Passing to a function expecting Distance
PrintDistance(10.5);
return 0;
}Risks of Implicit Conversions
While implicit conversions can make code concise, they frequently
introduce subtle bugs. The compiler will silently invoke constructors
where conversions make little conceptual sense. For example, if a custom
Vector class accepts an integer representing its capacity,
passing 4 where a vector is expected will silently create
an empty four-element vector rather than causing a compile-time type
mismatch error.
Explicit Conversions
To prevent unintended conversions, C++ provides the
explicit keyword. Adding explicit before a
constructor tells the compiler that the constructor cannot be used for
implicit conversions or copy-initialization.
class Distance {
public:
explicit Distance(double meters) : meters_(meters) {}
private:
double meters_;
};Once a constructor is marked explicit, attempting an
implicit conversion results in a compile-time error:
void PrintDistance(Distance d);
int main() {
// Compile error: conversion from 'double' to non-scalar type 'Distance' requested
Distance d1 = 5.0;
// Compile error: cannot convert 'double' to 'Distance'
PrintDistance(10.5);
// Valid: Direct initialization
Distance d2(5.0);
Distance d3{5.0};
// Valid: Explicit casting
PrintDistance(Distance(10.5));
PrintDistance(static_cast<Distance>(10.5));
return 0;
}With explicit, conversions require intentional syntax.
The caller must explicitly instantiate the object using direct
initialization ((), {}) or a cast
(static_cast).
Best Practices
- Default to
explicit: Modern C++ best practices dictate that single-argument constructors should be markedexplicitby default. - Allow implicit conversions sparingly: Reserve
implicit conversion constructors only for types where the source and
target represent the exact same conceptual entity with identical
semantics, such as wrapping numeric primitives in wrapper classes or
converting from
const char*to a custom string type.