How std::is_convertible Works in C++
In the C++ programming language,
std::is_convertible<From, To> is a standard type
trait defined in the <type_traits> header that
determines at compile time whether an expression of type
From can be implicitly converted to type To.
This article covers the underlying mechanics of
std::is_convertible, explaining how it evaluates conversion
viability through copy-initialization semantics, handles access control
and ambiguity, and applies special rules for types such as
void, arrays, and abstract classes.
The Conceptual Model: Imaginary Function Returns
The C++ standard defines the behavior of
std::is_convertible<From, To> using an imaginary
function definition. Specifically, given the types From and
To, the trait evaluates whether the following function
template is well-formed:
To test() {
return std::declval<From>();
}If a hypothetical function defined this way compiles without errors,
std::is_convertible<From, To>::value evaluates to
true. If the statement produces a substitution failure or a
semantic error, it evaluates to false.
Because the evaluation uses
return std::declval<From>();, it tests whether an
invented lvalue or rvalue expression of type From can
initialize a return value of type To.
Copy-Initialization vs. Direct-Initialization
The core distinction of std::is_convertible is that it
strictly tests implicit conversion via
copy-initialization contexts. It is functionally equivalent to testing
whether the declaration
To obj = std::declval<From>(); is valid.
This leads to critical behavioral distinctions:
- Implicit Constructors and Conversions: If
Tohas an implicit constructor acceptingFrom, or ifFromhas a non-explicit conversion operatoroperator To(), the trait yieldstrue. - Explicit Constructors and Conversions: If
Todeclares a constructor takingFromasexplicit, or ifFromdefinesexplicit operator To(),std::is_convertible<From, To>evaluates tofalse. In scenarios where direct-initialization (To obj(std::declval<From>());) is intended,std::is_constructible<To, From>must be used instead.
Access Control and Ambiguity Resolution
When evaluating conversion viability,
std::is_convertible respects C++ access control and
overload resolution rules:
- Access Control: If converting
FromtoTorequires access to a private or protected constructor, or involves private or protected base class inheritance, the conversion is considered ill-formed, and the trait evaluates tofalse. - Ambiguity: If multiple conversion paths exist—such
as ambiguous multiple inheritance conversions or competing conversion
operators with equal viability—the conversion is ambiguous. This causes
SFINAE (Substitution Failure Is Not An Error) mechanisms to fail,
yielding
false.
Handling Special Types
The standard specifies dedicated rules for edge-case types that cannot participate in normal function return statements:
- Void Types: Regular function semantics forbid
returning values of or to
voidin this context. However,std::is_convertibleexplicitly defines thatstd::is_convertible<cv void, cv void>istrue. Converting any other type tovoid, orvoidto any non-voidtype, always results infalse. - Arrays and Function Types: A function cannot return
an array type or a function type by value. Consequently, if
Tois an array type (e.g.,int[]orint[5]) or a function type (e.g.,void()), the trait always evaluates tofalse, regardless ofFrom. - Abstract Classes: Because an abstract class cannot
be instantiated or returned by value, any attempt to evaluate
std::is_convertible<From, AbstractClass>whereTois not a reference type evaluates tofalse. - Incomplete Types: Both
FromandTomust be complete types, complete arrays, or (possibly cv-qualified)void; passing an incomplete class type yields undefined behavior or a compile-time failure.
Implementation: Compiler Built-ins vs. SFINAE
In modern standard library implementations (such as libstdc++,
libc++, and MSVC STL), std::is_convertible is rarely
implemented purely through user-space SFINAE overloads like
sizeof tricks. Instead, modern compilers provide internal
compiler built-ins—such as __is_convertible_to(From, To) or
__is_convertible(From, To).
These intrinsics allow the compiler to perform the viability check directly inside the frontend AST analyzer. This avoids heavy template instantiation overhead, reduces compiler memory consumption, and guarantees strict conformity with standard corner cases such as non-trivial destructors and access-checking contexts.