std::any is a container that holds a single value of any type, where the type isn't known to the consumer at compile time and is recovered at runtime through std::any_cast. It is C++17's mechanism for storing a value of unknown type so a different piece of code can recover the type later. The header is <any>, the type lives in namespace std, and the entire chapter assumes C++17 or later. Compile examples with g++ -std=c++17.
C++ is a statically typed language. Most of the time, "what type does this hold" is a question the compiler can answer. std::optional<T> represents "a T or nothing". std::variant<Ts...> represents "one of these specific types". Both pin down the set at compile time.
A small number of problems can't be expressed that way. One example is the heterogeneous configuration store: a key/value map where every key has its own type, and the set of keys isn't fixed at compile time. A plugin system that lets third-party code attach arbitrary metadata to objects. A property bag on a game entity. A small message-passing layer where the payload type depends on which subsystem is talking.
Before C++17, the conventional approach was void* plus manual type tracking. The pattern has several problems: no destructor support, no ownership clarity, easy to read the wrong type, easy to leak.
Several things are wrong with that approach. The map doesn't own the values; the user has to remember which key maps to which type when deleting. A wrong static_cast is undefined behavior, not a runtime error. There's no way to ask the bag "what type is stored under this key?" The only safety comes from convention.
std::any was designed for this kind of loose, runtime-typed bag. It owns its value, runs the correct destructor automatically, and refuses to return a value as the wrong type.
Output:
The map now owns its values, the destructors run automatically when the map goes out of scope, and asking for the wrong type at any read site throws an exception instead of corrupting memory without a diagnostic. The trade-off is that std::any is more expensive than the typed alternatives and shouldn't be the default when the set of types is actually known.
A std::any can be created several ways.
Output:
std::in_place_type<T> builds the contained value directly inside the any, forwarding the remaining arguments to T's constructor. It skips the temporary that the assignment form would build. For expensive-to-construct types or types that aren't copyable (only movable), use in_place_type.
std::make_any<T>(args...) is a free-function helper that does the same thing:
Output:
Either form works; make_any reads more naturally when the contained type's name is the noun the call site emphasizes.
Quick Check: Does this compile and what does it print?
<details> <summary>Answer</summary>
It compiles, but it throws std::bad_any_cast at runtime. "USB Cable" is a const char*, not a std::string. The any stores the literal as a const char*, so any_cast<std::string> doesn't match. The fix is std::any a = std::string("USB Cable"); or std::any_cast<const char*>(a).
</details>
std::any exposes three core read operations, each with a different failure behavior.
| Operation | Returns | If type doesn't match | If any is empty |
|---|---|---|---|
a.has_value() | bool | n/a | false |
std::any_cast<T>(a) | T (by copy) | throws std::bad_any_cast | throws |
std::any_cast<T>(&a) | T* or nullptr | nullptr | nullptr |
a.type() | const std::type_info& | n/a | typeid(void) |
std::any_cast<T>(a) is the assertive form. It checks that the stored type is exactly T and returns a copy. Mismatch throws std::bad_any_cast.
Output:
The pointer form, std::any_cast<T>(&a), is the safe alternative. It takes the address of the any and returns a T* if the type matches, or nullptr if not. No exception, no copy.
Output:
The pointer form pairs with C++17's if-with-init. It tests and reads in one step with no exception machinery and no copy of the value.
The reference form, std::any_cast<T&>(a), returns a reference to the stored value and avoids the copy that the value form makes:
Output:
Modifying through the reference modifies the value stored inside the any. That is occasionally useful, though the by-value semantics of the value form may be a better fit for most call sites.
a.type() returns a const std::type_info& that identifies the stored type. It's useful when the consumer wants to switch on the type before attempting a cast:
Output:
std::variant handles this pattern better when the set of types is fixed. The any version above does nothing for any unhandled type, with no compile-time check to warn about coverage gaps. If a fourth supported type is added later, describe keeps compiling and produces "unknown type" for the new case without any error. Variant would have refused to compile.
Quick Check: Which line throws an exception?
<details> <summary>Answer</summary>
Line (2). The stored type is double, so any_cast<int>(a) throws std::bad_any_cast. Line (4) returns nullptr rather than throwing, because the pointer form of any_cast reports type mismatch as a null pointer.
</details>
The contents of a std::any can be replaced, cleared, or constructed in place. Each operation runs the destructor of whatever value was previously held.
Output:
In the transition from int to std::string between steps 2 and 3, the integer was destroyed and the string was constructed automatically, with no manual destructor calls or placement new. std::variant has the same property, applied to a closed set rather than an open one.
emplace<T>(args...) constructs a T directly inside the any from the given constructor arguments. It avoids building a temporary, which matters for non-trivially-constructible types.
reset() destroys the held value (if any) and makes the any empty. Assigning a default-constructed std::any{} has the same effect.
A common use for std::any is a metadata bag attached to a product or order. The store admin can attach arbitrary attributes, and the schema isn't fixed at compile time.
Output:
The class wraps a std::map<std::string, std::any> with a small templated API. The set method stores any type. The getIf method returns a typed pointer or nullptr. The caller has to know the right type for each key by convention, which is the price of working with std::any.
That convention is a real cost. The metadata schema lives in the documentation and the calling code, not in the type system. A read with the wrong type returns nullptr, which is a missed read unless the call site checks. For internal services that need stronger guarantees, this design isn't a good fit; for genuinely open extension surfaces (plugins, end-user attribute tagging, scripting bridges), it is what C++17 offers.
Internally, std::any is a small struct that holds either an inline buffer (for small values) or a pointer to a heap-allocated value (for larger ones). The exact threshold is implementation-defined, but the rough picture is consistent across libstdc++, libc++, and MSVC.
Small-object optimization (SOO) means types like int, double, bool, and small pointers usually live inline, with no heap allocation. Once the contained type's size or alignment exceeds the inline buffer, the any allocates on the heap and stores a pointer. On libstdc++, the inline buffer is typically 8 bytes; on libc++, it's typically 24 to 32 bytes. The exact threshold isn't standardized.
The implementation also stores a small function pointer (or vtable-like dispatch table) that knows how to copy, destroy, and identify the contained type. That's how any runs the right destructor without knowing the type statically: the destruction routine was captured when the value was assigned, and is called whenever the any is reset or destroyed.
A few measurable consequences:
any_cast does a type identifier comparison (typically typeid comparison, which can be a pointer compare on most platforms but a string compare on some).any copies the contained value (using the captured copy routine). For heap-allocated values, this means a new allocation.any itself is at least three pointers in size (the inline buffer plus the dispatch table pointer plus a discriminator).In tight loops, all of those costs add up. std::any is fine for low-frequency control paths (config lookups, plugin registration, message dispatch). It's a poor fit for hot inner loops or large collections of small values.
When a value's type isn't pinned down at compile time, three options compete. The choice depends entirely on whether the set of types is closed or open.
| Property | std::variant<Ts...> | std::any | Inheritance (unique_ptr<Base>) |
|---|---|---|---|
| Set of types | Closed, fixed | Open, any | Open, extendable |
| Type recovered at | Compile time (via visit) | Runtime (via any_cast) | Runtime (via dynamic_cast or virtual call) |
| Compile-time exhaustiveness | Yes (visit rejects missing cases) | No | No |
| Storage | Inline | Inline or heap, depending on size | Heap (via unique_ptr) |
| Common access pattern | std::visit | any_cast<T>(&a) | virtual call |
| Adding a new type | Breaks every visit site | No effect on existing code | No effect on existing code |
For a payment method with three alternatives, variant. For a render hierarchy with subclasses defined in client code, inheritance. For a configuration value bag whose schema lives outside the language, any. The lines between them aren't always obvious; the deciding question is "can I enumerate the set of types at compile time?" If yes, variant. If no, any or inheritance.
Many apparent "any" use cases turn out to be variant in disguise. A function that takes a "any kind of value" and dispatches on it often has a closed set of three or four real types, even if the design starts out feeling open. Try writing it as a variant first; use any only when the closed-set assumption genuinely breaks down.
Quick Check: A function receives a config value that's always either a number or a flag (int, double, or bool). Should it be a std::any or a std::variant?
<details> <summary>Answer</summary>
std::variant<int, double, bool>. The set of three types is closed and known at compile time. Variant gives compile-time exhaustiveness with std::visit, no heap allocation, and faster access than any_cast. Using std::any here would be giving up safety for no real flexibility.
</details>
Knowing when not to use std::any is the hard part. It looks tempting whenever a function takes "a value of some kind", but most such functions can be expressed more strictly.
Situations where std::any fits:
Situations where it doesn't:
std::variant.std::optional.std::function, which is more specific and faster.std::any should be a last resort when nothing else fits, not a first choice for any "type erasure" situation. It exists for cases that genuinely can't be typed at compile time, and the cost (per-cast type compare, per-assignment allocation for large values, no exhaustiveness checking) makes it expensive elsewhere.
A few common mistakes are worth calling out.
Type matching is exact, not convertible. any_cast<long>(a) where a holds an int throws. There's no implicit conversion from one numeric type to another. A value stored as int must be read as int. This is stricter than typical C++ value semantics.
Output:
**String literals are stored as const char*, not std::string.** std::any a = "Mouse Pad" stores a const char*, not a std::string. Casting to std::string then throws.
The output of a.type().name() is implementation-defined (e.g., PKc on g++ for const char*). Storing a literal and reading it as std::string doesn't work unless the literal was wrapped in std::string at the assignment site.
`std::any_cast<T>(a)` returns by value, which copies. For large objects, this copy can be expensive. Use the pointer form (any_cast<T>(&a)) or the reference form (any_cast<T&>(a)) to avoid copying.
An empty `std::any` is different from one holding a null pointer. std::any a; is empty; std::any a = static_cast<int*>(nullptr); holds a null pointer. The first has has_value() == false; the second has has_value() == true and stores a null pointer. The same distinction applies to std::optional<T*>.
`std::any` can hold non-copyable types only with caveats. The std::any interface requires copyability to support assignment from a value, but the value can be put in via emplace or in_place_type if it's at least movable. Reading it out by value with any_cast<T> then doesn't compile, because the cast tries to copy. Use the pointer or reference form for movable-only types.
Output:
The pointer form reads the value without copying it, which is what unique pointers need.
Calling `any_cast` on an empty `any` always throws (or returns null for the pointer form). Check has_value() first, or always use the pointer form.
A few concrete numbers help calibrate where std::any is appropriate.
The std::any object itself is roughly three pointers wide (about 24 bytes on a 64-bit platform). Most of that is the inline buffer used by the small-object optimization, plus the dispatch table pointer.
Small types (int, double, bool, raw pointers, sometimes std::string if SSO fits) live inline. No heap allocation on assignment, no heap allocation on copy. Move and copy of the any are fast.
Larger types (anything that doesn't fit the inline buffer) heap-allocate on every assignment. Copying the any allocates again. Destroying the any frees that allocation. For a container of any holding mostly large strings or vectors, the allocation traffic dominates.
any_cast does a type_info comparison. On libstdc++ and libc++ with RTTI enabled, this is usually a pointer comparison (the type_info objects are deduplicated by the linker), which is fast. On some platforms or in some build configurations, it can fall back to a string comparison of the mangled type name, which is slower.
For comparison: std::variant::index() plus std::get_if is a constant-time integer compare and a pointer load. That's typically an order of magnitude faster than any_cast in tight loops, which is one reason to prefer variant when the set of types is known.
| Operation | std::optional<T> | std::variant<Ts...> | std::any |
|---|---|---|---|
| Sizeof (typical, 64-bit) | sizeof(T) + 1 rounded up | sizeof(largest) + 4 to 8 | ~24 bytes plus heap for large T |
| Construction from value | Inline | Inline | Inline for small, heap for large |
| Read access | Direct read | Discriminator load + read | Type compare + read |
| Heap allocation | Never | Never (for variant itself) | For large contained types |
| Type-safety at access | Compile time | Compile time (visit) or runtime (get) | Runtime only |
The trend is consistent: more flexibility costs more at runtime. optional and variant give up flexibility in exchange for speed and safety. any gives up speed and safety in exchange for flexibility.
Q1: When would you choose `std::any` over `std::variant`?
When the set of types isn't known at compile time. std::variant requires the alternatives to be listed in the type, so adding a new alternative is a code change. std::any accepts any type whatsoever, with the type recovered at runtime through any_cast. The trade is that any gives up compile-time exhaustiveness checking, takes longer at every read, and may heap-allocate. Genuine fits are open-ended metadata bags, plugin systems, or message-passing layers where the payload type depends on subsystems chosen at runtime.
Q2: What's the difference between `std::any_cast<T>(a)` and `std::any_cast<T>(&a)`?
The value form, std::any_cast<T>(a), returns a copy of the contained value and throws std::bad_any_cast if the stored type isn't exactly T. The pointer form, std::any_cast<T>(&a), takes the address of the any and returns a T* if the type matches, or nullptr otherwise. The pointer form is non-throwing and doesn't copy, which fits "test and read" patterns and movable-only types. The value form is convenient when the type is known and a copy is acceptable.
**Q3: Why does std::any a = "USB Cable" store a const char* instead of a std::string?**
Because "USB Cable" is a const char[10] that decays to const char* in the assignment. std::any stores exactly what it was given: the decayed pointer. There's no implicit conversion to std::string, because any deliberately doesn't perform conversions; it stores the exact type. A subsequent any_cast<std::string> fails. The fix is to wrap the literal explicitly: std::any a = std::string("USB Cable");.
Q4: Does `std::any` allocate on the heap?
It depends on the contained type's size. Small types that fit in the implementation's inline buffer (typically 8 to 32 bytes, depending on the standard library) live inline with no allocation. Larger types are heap-allocated, with the any storing a pointer to the heap value. Every assignment that crosses the inline threshold allocates, every copy allocates again, and every reset frees the allocation. For int, double, bool, no allocation. For a large struct, yes.
Q5: How does `std::any` run the right destructor for whatever type it's holding?
When a value is assigned, the implementation captures a function pointer (or stores a small dispatch table) that knows how to copy, destroy, and identify that specific type. When the any is reset or destroyed, it calls the captured destruction routine. This is the same mechanism behind std::function and other type-erased containers: the type-specific operations are stored alongside the value, so the contents can be manipulated correctly without knowing the type statically.
Exercise 1: Create a std::any holding an int, then a std::string, then reset it. Print has_value() after each step.
Expected Output:
<details> <summary>Solution</summary>
</details>
Exercise 2: What does this code print, and why?
Expected Output:
<details> <summary>Solution</summary>
The literal "Wireless Headphones" is const char[20] that decays to const char* in the assignment. std::any stores it as const char*, not std::string. To store as std::string, write std::any a = std::string("Wireless Headphones");.
</details>
Exercise 3: Write a function describe(const std::any&) that prints the stored value if it's an int, double, or std::string, and prints "unknown" otherwise. Use std::any_cast<T>(&a) (the pointer form) so the function never throws. Test with one value of each supported type plus a bool.
Expected Output:
<details> <summary>Solution</summary>
</details>
Exercise 4: Fix the bug. The code is supposed to print the discount label, but it throws.
Expected Output:
<details> <summary>Solution</summary>
The original code stored "SAVE10" as const char*. The cast to std::string therefore failed. Wrapping the literal in std::string at the assignment makes the stored type match what the cast expects.
</details>
Exercise 5: Predict the output.
Expected Output:
<details> <summary>Solution</summary>
std::any copies the contained value. After std::any b = a, b holds its own int with the value 42. Modifying a afterward doesn't affect b. any has value semantics, not reference semantics; each copy holds its own independent value.
</details>
Exercise 6: Build a Settings class with two methods: set(const std::string& key, std::any value) and getString(const std::string& key) that returns std::optional<std::string> (empty if the key doesn't exist or the stored type isn't std::string). Test it by storing both string and non-string values, then reading both.
Expected Output:
<details> <summary>Solution</summary>
The example combines std::any (for the heterogeneous store) with std::optional (for the typed read result). That layering is the standard way to wrap an any-based store in a typed API.
</details>
Exercise 7: Modify a std::any holding a std::string in place, without copying it out. Add " (limited edition)" to the end of the contained string and print the result.
Expected Output:
<details> <summary>Solution</summary>
The reference form of any_cast returns a reference to the stored value. Appending to that reference modifies the value held inside the any directly, no copy involved.
</details>
Exercise 8: A coworker proposes using std::any to represent a payment method that's always one of Card, UPI, or Cash. Argue against it. What's the better C++17 type, and why?
Expected Output:
<details> <summary>Solution</summary>
std::variant<Card, UPI, Cash> is the better fit. The set of payment methods is closed and known at compile time, which is exactly the case std::variant is built for. Variant gives compile-time exhaustiveness through std::visit: adding a new payment method later forces every visit site to be updated, otherwise the code doesn't compile. std::any would give that up. Variant also avoids the heap allocation that any performs for non-trivially-sized types, the per-cast type comparison, and the silent-mismatch failure mode where reading the wrong type returns nullptr without warning. The only reason to use any here would be if third-party code could add new payment methods at runtime, which isn't the case for a closed checkout flow.
</details>
10 quizzes