12 / 163 · C++11 · 8 min
explicit: Leave Conversion Intent at the Call Site
explicit stops a constructor or conversion function from participating in some implicit conversions, but still allows direct initialization and explicit conversion. It is suitable for protecting units, capacities, and resource wrapper types. explicit operator bool can also support contextual conversion to bool while avoiding accidental numeric conversion.
In this lesson
Why conversion should not always be automatic
Suppose Duration has only a constructor that takes int. Without explicit, a function that accepts Duration may accept a raw integer, and assignment or return contexts may quietly construct a temporary. That saves a few characters but hides the integer's unit, range, and conversion cost.
After you mark the constructor explicit, callers can still write Duration{3} or Duration(3), but they cannot pass 3 as a Duration argument directly. It expresses “construction is allowed, but you must say so at the call site.” It does not forbid this constructor, and it does not make the object non-copyable. The point is documentation with teeth: a unit, a capacity, or a resource wrapper should not appear from a bare number in an argument list. Direct initialization remains available so legitimate construction is still a short, local spelling.
Distinguish initialization contexts
T x(3) and T x{3} are direct initialization and may consider an explicit constructor; copy initialization T x = 3 does not use it. T x = {3} is copy-list-initialization; if the selected constructor is explicit, the program is ill-formed. Braces themselves do not mean that every explicit restriction disappears.
explicit is not only for single-argument constructors; multi-argument constructors and constructors with default arguments may also need to constrain list conversions. The example uses is_constructible and is_convertible to distinguish “can be constructed explicitly” from “can convert implicitly,” and then shows an object in actual use. Direct initialization asks whether the constructor may be called as a construction. Implicit conversion asks whether the type may appear where another type was written. Keep those questions separate when you choose explicit, especially around list initialization, which has its own copy versus direct rules.
Controlled boolean conversion
explicit operator bool() can be used in if, while, and logical operations, and can be invoked with static_cast<bool>; it will not casually join unrelated arithmetic the way an ordinary implicit integer conversion would. A resource wrapper can therefore express validity naturally without being treated as an ordinary count.
C++20 explicit(condition) allows a generic wrapper to decide whether to be explicit based on convertibility of the underlying type, which suits careful library interfaces. Everyday business classes should first pick a simple, clear policy: when there is information loss, a unit change, or a non-obvious cost, usually require the caller to construct explicitly. Contextual conversion to bool is the narrow exception that keeps if (handle) readable. Ordinary copy initialization to bool, and arithmetic that would use a numeric conversion, stay blocked so the wrapper does not leak into integer expressions.
Pitfalls
- explicit does not check integer ranges and does not prevent mistakes inside the constructor body; business invariants still need separate checks.
- explicit operator bool supports contextual conversion to bool, but ordinary copy initialization bool b = object still does not accept that explicit conversion.
Run an example
Minimum C++11 · complete program · Download .cpp
#include <cassert>
#include <iostream>
#include <type_traits>
class Count {
int value_;
public:
explicit Count(int value) : value_(value) {}
int value() const { return value_; }
explicit operator bool() const { return value_ != 0; }
};
int main() {
static_assert(std::is_constructible<Count, int>::value, "direct construction");
static_assert(!std::is_convertible<int, Count>::value, "no implicit conversion");
Count count{3};
const bool active = static_cast<bool>(count);
assert(active);
if (count) std::cout << count.value() << '\n';
}
Compile locally
g++ -std=c++11 -Wall -Wextra -Wpedantic -pthread basics-explicit.cpp -o example && ./exampleExpected result
3
CHECK YOUR UNDERSTANDING
Close the answer. Explain it.
For explicit Count(int), which of Count a(2), Count b{2}, Count c = 2, and Count d = {2} are valid? If a function accept(Count) should be given 2, how should you write the call?
Show a reference answer
a and b are valid: direct initialization and direct list-initialization. c cannot implicitly invoke the explicit constructor; d is ill-formed because copy-list-initialization selected an explicit constructor. The call should be written accept(Count{2}), performing construction clearly at the call site and keeping the interface's purpose of requiring conversion intent to be expressed.
Check the sources
Drafts and official chapters change. The version mark is only the example’s minimum.