Skip to content
C++
Library
since C++17
Basic

std::any

Type-safe, type-erased container for a single value of any copy-constructible type, with runtime type queries and checked casts.

std::anysince C++17

A type-safe container that stores a single value of any copy-constructible type and exposes runtime type identity via std::type_info, with checked value extraction through std::any_cast.

Overview

std::any (C++17, <any>) is the type-safe successor to void* for heterogeneous value storage. It carries the stored type's identity alongside the value so every extraction is a checked operation, never a silent misinterpret. The design originated in Boost (boost::any); C++17 standardised the interface.

Three vocabulary types address related problems:

ProblemTool
Closed set of alternatives, fixed at compile timestd::variant (C++17)
Optional value of a single known typestd::optional (C++17)
Open set β€” any type determined at runtimestd::any (C++17)

Storage model and SBO

std::any implementations universally apply small buffer optimisation (SBO): types that fit within an internal buffer (typically 1–3 pointer-sized words β€” exact threshold is implementation-defined and not queryable) are stored inline with no heap allocation. Types exceeding the buffer, or types that are not nothrow_move_constructible, spill to the heap via new. You cannot portably determine which path was taken.

This means std::any is not zero-overhead. A heap-backed any carries an allocation on construction and a deallocation on destruction. Keep this in mind before placing any in tight loops or high-frequency data structures.

Syntax

cpp
#include <any>   // C++17

// Construction
std::any a;                                             // empty
std::any b = 42;                                        // holds int (likely SBO)
std::any c = std::string("hello");                      // holds std::string
std::any d = std::make_any<std::vector<int>>(4, 0);     // C++17: {0,0,0,0}

// In-place construction β€” avoids an intermediate move
a.emplace<std::pair<int,int>>(3, 7);                    // constructs pair in-place

// std::any_cast β€” five overloads
int  n  = std::any_cast<int>(b);            // copy; throws std::bad_any_cast on mismatch
int& r  = std::any_cast<int&>(b);           // lvalue ref; same throw guarantee
int  mv = std::any_cast<int>(std::move(b)); // move out; b left in valid unspecified state
int* p  = std::any_cast<int>(&b);           // pointer; returns nullptr on mismatch (no throw)
const int* cp = std::any_cast<int>(&std::as_const(b)); // const pointer; no throw

// Introspection
b.has_value();        // true
b.type();             // const std::type_info& β†’ typeid(int)
std::any{}.type();    // typeid(void) β€” guaranteed for empty any

// Reset
b.reset();            // b becomes empty
b = std::any{};       // equivalent

Examples

Heterogeneous property bag

A configuration registry where values can be strings, integers, durations, or nested structures without a fixed schema β€” a realistic motivation for std::any:

cpp
#include <any>
#include <string>
#include <unordered_map>
#include <stdexcept>

class PropertyBag {
    std::unordered_map<std::string, std::any> props_;

public:
    template<typename T>
    void set(std::string key, T&& value) {
        props_.insert_or_assign(std::move(key), std::forward<T>(value));
    }

    // Throws std::out_of_range or std::bad_any_cast on failure
    template<typename T>
    T& get(const std::string& key) {
        auto it = props_.find(key);
        if (it == props_.end())
            throw std::out_of_range("key not found: " + key);
        return std::any_cast<T&>(it->second);
    }

    // No-throw conditional access β€” prefer this in non-exceptional paths
    template<typename T>
    T* get_if(const std::string& key) noexcept {
        auto it = props_.find(key);
        return it != props_.end() ? std::any_cast<T>(&it->second) : nullptr;
    }
};

PropertyBag cfg;
cfg.set("timeout_ms",  5'000);
cfg.set("endpoint",    std::string("https://api.example.com"));
cfg.set("retry",       true);

int  timeout = cfg.get<int>("timeout_ms");    // 5000
bool retry   = cfg.get<bool>("retry");        // true

if (auto* ep = cfg.get_if<std::string>("endpoint"))
    // *ep is valid here; no exception path
    connect(*ep);

Type-dispatching without exceptions

Use the pointer overload for expected type checks; reserve the value/reference overloads for genuinely unexpected mismatches:

cpp
#include <any>
#include <string>
#include <iostream>

void describe(const std::any& v) {
    if (!v.has_value()) {
        std::cout << "(empty)\n";
    } else if (const auto* i = std::any_cast<int>(&v)) {
        std::cout << "int: " << *i << '\n';
    } else if (const auto* s = std::any_cast<std::string>(&v)) {
        std::cout << "string: " << *s << '\n';
    } else if (const auto* d = std::any_cast<double>(&v)) {
        std::cout << "double: " << *d << '\n';
    } else {
        // type().name() is implementation-defined β€” use only for diagnostics
        std::cout << "unknown type: " << v.type().name() << '\n';
    }
}

Moving out of std::any

When you own the any and want the value without a copy, use the rvalue overload:

cpp
std::any a = std::string(1'000, 'x');

// Moves the string out β€” no string heap copy
std::string s = std::any_cast<std::string>(std::move(a));
a.reset(); // a.has_value() after a move is unspecified; reset explicitly if needed

Accumulating heterogeneous values

std::vector<std::any> is a natural heterogeneous sequence β€” used in plugin systems, scripting value types, and serialisation intermediaries:

cpp
std::vector<std::any> row;
row.emplace_back(42);
row.emplace_back(3.14);
row.emplace_back(std::string("label"));
row.emplace_back(true);

for (const auto& cell : row)
    describe(cell);

Best Practices

Prefer std::variant for closed type sets. If the universe of types is fixed at compile time, variant provides exhaustiveness via std::visit, no heap allocation, and compile-time type checking. Reach for std::any only when the type set is genuinely open at compile time.

Always use the pointer overload for conditional checks. std::any_cast<T>(&a) is noexcept and returns nullptr on mismatch. The value overload throws std::bad_any_cast. Exceptions should signal truly unexpected failures, not routine type inspection.

Construct in-place to avoid redundant moves. Both std::make_any<T>(args...) and a.emplace<T>(args...) construct the value directly in the any's storage:

cpp
// Preferred β€” constructs vector directly in any's storage
auto a = std::make_any<std::vector<std::string>>(100, "slot");

// Worse β€” constructs vector, then moves it in
std::vector<std::string> tmp(100, "slot");
std::any b = std::move(tmp);

Cache a reference for repeated in-place mutation. Casting repeatedly in a loop re-runs the typeid check each time. A single reference cast amortises the cost:

cpp
std::any counter = 0;
int& n = std::any_cast<int&>(counter); // one checked cast
for (int i = 0; i < 1'000'000; ++i)
    ++n; // direct reference β€” no per-iteration cast

Common Pitfalls

Move-only types cannot be stored

std::any requires the contained type's decayed type to be copy-constructible. Move-only types fail at compile time:

cpp
std::any a = std::make_unique<int>(42); // ❌ ill-formed β€” unique_ptr is not CopyConstructible

Use std::shared_ptr (which is copyable) or a custom type-erased interface that owns the lifetime.

References are not preserved β€” only values

std::decay_t strips references and top-level cv-qualifiers before storing. You cannot store a raw reference in std::any:

cpp
int x = 42;
std::any a = x;           // stores a copy of x β€” x and a are independent
std::any b = std::ref(x); // stores std::reference_wrapper<int>, not int&

// Correct extraction when using reference_wrapper:
x = 99;
int val = std::any_cast<std::reference_wrapper<int>>(b).get(); // 99 β€” via wrapper

type().name() is not portable

The string returned by type().name() is implementation-defined and typically mangled. On GCC, typeid(int).name() returns "i"; on MSVC it returns "int". Never use it for serialisation, protocol negotiation, or user-facing output. Use it only for debugging diagnostics.

has_value() after a move is unspecified

Moving out of an any via std::any_cast<T>(std::move(a)) leaves a in a valid but unspecified state β€” it may still report has_value() == true with a moved-from value. Reset explicitly if you need a guaranteed-empty state:

cpp
std::string s = std::any_cast<std::string>(std::move(a));
// a's state is unspecified here
a.reset(); // now a.has_value() == false, guaranteed

Not suitable for performance-critical paths

Each any_cast performs a typeid comparison (cheap, but not free). Construction of a large type allocates on the heap. If you're processing millions of heterogeneous values per second, profile against std::variant with std::visit β€” the closed-set version typically wins on both branch prediction and allocation cost.

See Also

  • std::variant β€” discriminated union with compile-time exhaustiveness; prefer over any when the type set is known
  • std::optional β€” nullable single-type wrapper with no type-erasure overhead
  • std::bad_any_cast β€” exception thrown by the value/reference any_cast overloads on type mismatch
  • Type Erasure idiom β€” hand-rolled type erasure for reference support, performance tuning, or move-only semantics