C++ explicit Keyword for Constructors Explained

This article explores the purpose and necessity of the explicit keyword in C++, focusing on how it applies to single-argument constructors. You will learn how C++ handles implicit type conversions by default, the unintended bugs and ambiguities these conversions can introduce, and how marking constructors as explicit prevents the compiler from performing unwanted automatic type casting, resulting in safer and more maintainable code.


The Default Behavior: Implicit Conversion

In C++, any constructor that can be invoked with a single argument acts by default as an implicit conversion operator. This means the compiler is allowed to use that constructor silently to convert a value of the argument's type into an instance of the class.

Consider the following example:

class Buffer {
public:
    Buffer(int size) {
        // Allocates a buffer of 'size' bytes
    }
};

void processBuffer(const Buffer& buf) {
    // Process the buffer
}

Because Buffer(int size) is not declared explicit, the compiler permits operations such as:

Buffer b = 10;        // Compiles: Implicitly converts int 10 to Buffer(10)
processBuffer(50);    // Compiles: Implicitly converts int 50 to Buffer(50)

While this can occasionally be convenient, it often produces unintuitive and buggy behavior. Passing an integer like 50 to a function expecting a Buffer object hides logic errors, as the reader or author of processBuffer(50) might assume 50 represents buffer data rather than an allocated size.

The Role of the explicit Keyword

The explicit keyword is a declaration specifier applied to constructors to suppress implicit type conversions and copy-initialization syntax.

When you prepend explicit to a constructor:

class Buffer {
public:
    explicit Buffer(int size) {
        // Allocates a buffer of 'size' bytes
    }
};

The compiler will reject implicit conversions:

Buffer b1 = 10;       // Compiler Error: conversion from 'int' to non-scalar type 'Buffer' requested
processBuffer(50);    // Compiler Error: could not convert '50' from 'int' to 'Buffer'

Valid Instantiations with explicit

Applying explicit does not prevent you from constructing the object with that argument; it simply requires direct initialization syntax or an explicit cast:

Buffer b1(10);                         // OK: Direct-initialization
Buffer b2{10};                         // OK: Direct-list-initialization
Buffer b3 = Buffer(10);                // OK: Explicit temporary created
processBuffer(Buffer(50));             // OK: Explicit conversion
processBuffer(static_cast<Buffer>(50));// OK: Explicit cast

When to Use explicit

As a general best practice in modern C++:

  1. Mark single-argument constructors as explicit by default. This prevents surprising conversions where types do not conceptually match (e.g., converting an integer to a memory buffer, file handle, or collection size).
  2. Omit explicit only when designing transparent wrapper types. If the constructor argument represents a natural, lossless equivalence—such as converting a const char* to a custom String class, or a numeric type to a Complex number class—omitting explicit may be justified to allow idiomatic assignment and function calls.

By enforcing explicit instantiation, you turn silent runtime misbehaviors into clear, immediate compile-time errors.