Three things to take away:
- A non-
explicitconstructor can enable implicit conversion into a class type. - Only one user-defined conversion is allowed in an implicit conversion sequence.
- Mark single-argument constructors
explicitunless implicit conversion is genuinely intended.
The problem
Consider the following:
#include <iostream>
class RareMacGuffin {
private:
int m_rarityLevel {};
public:
RareMacGuffin(int rarityLevel)
: m_rarityLevel { rarityLevel }
{
}
int rarityLevel() const{
return m_rarityLevel;
}
};
void inspect(RareMacGuffin item){
std::cout << "Rarity level: " << item.rarityLevel() << '\n';
}
int main(){
inspect(true);
}
Terminal output:
Rarity level: 1
This compiles. Even though it is semantically meaningless.
C++ uses implicit conversions and converts true to int, then uses the constructor:
RareMacGuffin(int rarityLevel)
to create a temporary RareMacGuffin.
The result is technically correct but obviously it’s not what we want.
What is a converting constructor?
A converting constructor is a constructor that allows an object to be created from another type through implicit conversion. This is what occurred in the previous example.
A RareMacGuffin(int rarityLevel) could be created by constructing it with a bool type.
A useful implicit conversion
Not every converting constructor is bad.
For example, a lightweight type that wraps a string-like value may reasonably accept std::string_view:
#include <iostream>
#include <string>
#include <string_view>
class Username {
private:
std::string m_name {};
public:
Username(std::string_view name)
: m_name { name }
{
}
const std::string& value() const{
return m_name;
}
};
void greet(Username username){
std::cout << "Hello, " << username.value() << '\n';
}
int main(){
greet(Username { "Ada" });
}
Terminal output:
Hello, Ada
This version is explicit at the call site:
greet(Username { "Ada" });
The reader can see that a Username is being constructed.
If the constructor were not explicit, the function may also accept a std::string_view implicitly. Whether that is desirable depends on the API.
The design question is:
Should callers be allowed to pass this other type as if it were already my class type?
If the answer is no, use explicit.
Only one user-defined conversion is allowed
C++ allows at most one user-defined conversion in an implicit conversion sequence.
This prevents long chains of automatic conversions from silently constructing distant types.
Consider this example:
#include <iostream>
class UserId {
private:
int m_value {};
public:
UserId(int value)
: m_value { value }
{
}
int value() const{
return m_value;
}
};
class UserProfile {
private:
UserId m_id;
public:
UserProfile(UserId id)
: m_id { id }
{
}
void print() const{
std::cout << "User ID: " << m_id.value() << '\n';
}
};
void printProfile(UserProfile profile){
profile.print();
}
int main(){
printProfile(42);
}
Compiler error, abridged:
could not convert '42' from 'int' to 'UserProfile'
The call looks simple:
printProfile(42);
But making it work implicitly would require two user-defined conversions:
int → UserId
UserId → UserProfile
C++ does not allow that chain implicitly.
The fix is to make the construction explicit:
#include <iostream>
class UserId {
private:
int m_value {};
public:
UserId(int value)
: m_value { value }
{
}
int value() const{
return m_value;
}
};
class UserProfile {
private:
UserId m_id;
public:
UserProfile(UserId id)
: m_id { id }
{
}
void print() const{
std::cout << "User ID: " << m_id.value() << '\n';
}
};
void printProfile(UserProfile profile){
profile.print();
}
int main(){
printProfile(UserProfile { UserId { 42 } });
}
Terminal output:
User ID: 42
Now the conversions are not hidden.
The caller says exactly what is being constructed:
UserProfile { UserId { 42 } }
That is clearer and safer.
Blocking implicit construction with explicit
The explicit keyword prevents a constructor from being used for implicit conversions.
Return to the RareMacGuffin example:
#include <iostream>
class RareMacGuffin {
private:
int m_rarityLevel {};
public:
explicit RareMacGuffin(int rarityLevel)
: m_rarityLevel { rarityLevel }
{
}
int rarityLevel() const{
return m_rarityLevel;
}
};
void inspect(RareMacGuffin item){
std::cout << "Rarity level: " << item.rarityLevel() << '\n';
}
int main(){
inspect(true);
}
Compiler error, abridged:
could not convert 'true' from 'bool' to 'RareMacGuffin'
The constructor still exists:
explicit RareMacGuffin(int rarityLevel)
But it can no longer be used implicitly. There is no accidental conversion from bool.
Takeaway
A converting constructor allows C++ to create a class object from another type.
That can be convenient, but it permits construction of meaningless objects. explicit can be used to enforce object creation.