The Explicit Keyword

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:

  1. Single-Argument Constructors: Apply explicit to single-argument constructors that aren't meant for implicit conversions.
  2. Conversion Operators: Use explicit with conversion operators to prevent implicit conversions between types.
  3. Consistency: Consider the overall design and maintain consistency within your codebase. Applying explicit uniformly across similar scenarios can improve code clarity.