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;
}
Best Practice
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.