std::common_type vs std::conditional in C++

This article explains the core distinctions between std::common_type and std::conditional, two foundational type traits provided in the C++ <type_traits> header. While both utilities select a type at compile time, they serve entirely different purposes: std::conditional acts as a compile-time if/else branch based on a boolean value, whereas std::common_type deduces a single common type to which all listed types can be implicitly converted.


What is std::conditional?

std::conditional is essentially a ternary operator (?:) evaluated at compile time for types. It takes three template arguments: a bool condition, a true-case type, and a false-case type.

  • Syntax: std::conditional<bool Condition, class T, class F>::type (or std::conditional_t<Condition, T, F> in C++14 and later).
  • Behavior: If Condition evaluates to true, the resulting type is T. If false, the resulting type is F.

Example:

#include <type_traits>
#include <cstdint>

// Selects uint64_t if 64-bit systems are targeted, otherwise uint32_t
using PlatformInt = std::conditional_t<(sizeof(void*) == 8), std::uint64_t, std::uint32_t>;

std::conditional does not perform any deduction, conversion, or compatibility checks between T and F. It strictly branches based on the boolean condition.


What is std::common_type?

std::common_type computes a shared type to which all passed types can be implicitly converted. It accepts a parameter pack of types rather than a boolean flag.

  • Syntax: std::common_type<class... Types>::type (or std::common_type_t<Types...> in C++14 and later).
  • Behavior: It determines the result type of a ternary expression involving instances of each type: decltype(true ? std::declval<T1>() : std::declval<T2>()). If no implicit conversion path exists between the types, std::common_type results in a substitution failure (SFINAE).

Example:

#include <type_traits>

// Deduces double because int implicitly converts to double
using ResultType = std::common_type_t<int, double>; // ResultType is double

// Works with multiple types
using WideType = std::common_type_t<short, int, float, double>; // WideType is double

std::common_type is also extensible; you can specialize it for user-defined types (e.g., custom numeric or dimensional types) to define how they interoperate.


Key Distinctions

Feature std::conditional std::common_type
Primary Role Type selection based on a boolean expression. Type deduction based on implicit convertibility.
Input Parameters Exactly one bool condition and two types. Variadic (one or more types).
Relationship of Types Types can be completely unrelated. Types must be mutually convertible to a common target.
Custom Specialization Not intended to be specialized. Can be specialized for user-defined types.

When to Use Which

  • Use std::conditional when you need to toggle an implementation detail or data type based on a static compile-time check (e.g., selecting fixed-width types depending on platform architecture, or picking an allocator strategy based on a template policy).
  • Use std::common_type when writing generic functions or classes that accept mixed types and need to deduce a safe return type (e.g., heterogeneous arithmetic operations like operator+, or determining the element type for a collection constructed from mixed inputs).