std::is_same vs std::is_base_of in C++

In modern C++, compile-time type introspection is primarily handled by the <type_traits> header, where std::is_same and std::is_base_of are two fundamental tools for evaluating relationships between types. While both return a compile-time boolean value, they serve fundamentally different purposes: std::is_same checks whether two types are strictly identical, whereas std::is_base_of tests for an inheritance hierarchy between class types. This article breaks down how each trait works, their key technical differences, edge cases, and appropriate use cases.

Understanding std::is_same

std::is_same<T, U> checks for exact type equivalence at compile time. It yields true if T and U refer to the exact same type, and false otherwise.

Crucially, std::is_same is sensitive to type qualifiers, references, and distinct types even if they are structurally compatible:

  • Const and Volatile: std::is_same<int, const int>::value is false.
  • References: std::is_same<int, int&>::value is false.
  • Inheritance: std::is_same<Base, Derived>::value is always false.
  • Type Aliases: using MyInt = int; followed by std::is_same<int, MyInt>::value is true, because aliases do not create distinct types.

Understanding std::is_base_of

std::is_base_of<Base, Derived> determines whether Base is a base class of Derived. It yields true if:

  1. Base is an unambiguous and accessible or inaccessible base of Derived.
  2. Both Base and Derived are the same non-union class type (regardless of cv-qualifiers).

If either type is a primitive, a union, or a non-class type, std::is_base_of evaluates to false. Top-level const and volatile qualifiers are ignored when comparing types with std::is_base_of.

Core Differences

Feature std::is_same<A, B> std::is_base_of<A, B>
Relationship Tested Exact type identity Inheritance hierarchy (is-a relationship)
Base and Derived Classes false true
Identical Class Types true true (if class type)
Identical Primitive Types true (e.g., int, int) false (e.g., int, int)
cv-qualifiers (const/volatile) Strict (int \(\neq\) const int) Ignored on class types
Private/Protected Inheritance N/A true (evaluates relationship regardless of access)

Practical Code Comparison

#include <iostream>
#include <type_traits>

struct Base {};
struct Derived : Base {};

int main() {
    // 1. Inheritance verification
    std::cout << std::is_same<Base, Derived>::value;    // Outputs 0 (false)
    std::cout << std::is_base_of<Base, Derived>::value; // Outputs 1 (true)

    // 2. Exact same class
    std::cout << std::is_same<Base, Base>::value;       // Outputs 1 (true)
    std::cout << std::is_base_of<Base, Base>::value;    // Outputs 1 (true)

    // 3. Modifiers (const)
    std::cout << std::is_same<Base, const Base>::value;    // Outputs 0 (false)
    std::cout << std::is_base_of<Base, const Base>::value; // Outputs 1 (true)

    // 4. Fundamental types
    std::cout << std::is_same<int, int>::value;       // Outputs 1 (true)
    std::cout << std::is_base_of<int, int>::value;    // Outputs 0 (false)
}

When to Use Which

  • Use std::is_same when implementing template specializations, verifying template argument matches, or writing concepts that demand exact type conformity. It ensures that no implicit conversions, polymorphism, or unintended base-class slicing occur.
  • Use std::is_base_of when constraining generic algorithms to an object-oriented class hierarchy. It is commonly used in SFINAE (std::enable_if) or C++20 concepts to accept any object belonging to an inheritance tree, enabling compile-time polymorphism.