Overview
C++17 introduced three utility types commonly referred to as the "trio": std::optional, std::any, and std::variant. This article focuses on std::any, a type-safe container designed to hold a single value of any copy-constructible type.
The class is defined in the <any> header:
namespace std {
class any;
}
Unlike template classes, std::any is a concrete class that acts as a type-erasing wrapper. It can store fundamental types (int, double, char) as well as compound types (classes, structs). The key advantage is that std::any maintains type information at runtime, enabling type-safe retrieval while allowing arbitrary type storage during declaration.
Construction Methods
Default and Direct Initialization
An empty std::any can be created using the default constructor:
std::any container;
Values can be assigned directly upon construction:
std::any val1 = 100; // stores int
std::any val2 = "sample text"; // stores const char*
When storing types different from the initializer, use std::in_place_type:
std::any val3{std::in_place_type<double>, 3.14159};
std::any val4{std::in_place_type<std::string>, "initialized"};
Note that array types undergo decay. The following stores a const char*:
std::any val5{std::in_place_type<const char[6]>, "12345"};
For types requiring multiple constructor arguments, either construct the object first or use in_place_type:
std::any v1{std::complex{1.0, 2.0}};
std::any v2{std::in_place_type<std::complex<double>>, 1.0, 2.0};
Initializer lists are also supported:
auto cmp = [](int x, int y) { return std::abs(x) < std::abs(y); };
std::any v3{std::in_place_type<std::set<int, decltype(cmp)>>,
{5, -3, 9, 1, -7}, cmp};
std::make_any Helper Function
Two overloads are available:
// Overload 1: Direct construction with arguments
template<class T, class... Args>
std::any make_any(Args&&... args);
// Overload 2: Construction with initializer list
template<class T, class U, class... Args>
std::any make_any(std::initializer_list<U> il, Args&&... args);
The template argument must be explicitly specified:
#include <any>
#include <complex>
#include <functional>
#include <iostream>
#include <string>
int main() {
auto data1 = std::make_any<std::string>("Greetings!");
auto data2 = std::make_any<std::complex<double>>(1.5, 3.7);
std::cout << std::any_cast<std::string&>(data1);
std::cout << std::any_cast<std::complex<double>&>(data2) << '\n';
using Callback = std::function<void()>;
// Attempt 1: Direct assignment (fails)
std::any data3 = []{ std::cout << "Anonymous function\n"; };
std::cout << "data3.type() = \"" << data3.type().name() << "\"\n";
try {
std::any_cast<Callback>(data3)();
}
catch (std::bad_any_cast const&) {
std::cout << "Type mismatch detected\n";
}
// Attempt 2: Using make_any (succeeds)
auto data4 = std::make_any<Callback>([]{ std::cout << "Callback executed\n"; });
std::cout << "data4.type() = \"" << data4.type().name() << "\"\n";
std::any_cast<Callback>(data4)();
}
Output:
Greetings!
(1.5,3.7)
data3.type() = "Z4mainEUlvE_"
Type mismatch detected
data4.type() = "St8functionIFvvEE"
Callback executed
Assignment Operators
Three assignment operators are available:
// Copy assignment
any& operator=(const any& other);
// Move assignment
any& operator=(any&& other) noexcept;
// Value assignment with type deduction
template<typename ValueType>
any& operator=(ValueType&& rhs);
The third overload participates in overload resolution only when std::decay_t<ValueType> differs from any and is copy-constructible:
std::any x = 999;
std::any y = "original";
x = y; // copy assignment
y = 2.71828; // value assignment
x = std::make_any<std::complex>(5.0, 1.0); // move assignment
Retrieving Values with std::any_cast
Five overloads exist:
// Reference-based casts (throw on mismatch)
template<class T> T any_cast(const any& operand);
template<class T> T any_cast(any& operand);
template<class T> T any_cast(any&& operand);
// Pointer-based casts (return nullptr on mismatch)
template<class T> const T* any_cast(const any* operand) noexcept;
template<class T> T* any_cast(any* operand) noexcept;
Extracting stored values requires casting to the correct type:
std::any_cast<std::string>(val) // copy
std::any_cast<std::string&>(val) // mutable reference
std::any_cast<const std::string&>(val) // const reference
Casting to the wrong type throws std::bad_any_cast:
try {
auto str = std::any_cast<std::string>(val);
}
catch (std::bad_any_cast& ex) {
std::cerr << "Caught: " << ex.what() << '\n';
}
To avoid unnecessary temporary object creation, prefer reference or pointer casts:
// Avoid: creates temporary string
std::cout << std::any_cast<std::string>(val);
// Better: const reference
std::cout << std::any_cast<const std::string&>(val);
// Modify value directly
std::any_cast<std::string&>(val) = "new content";
Pointer-based casts allow safe checking without exceptions:
if (auto ptr = std::any_cast<std::string>(&val)) {
std::cout << "Found: " << *ptr << '\n';
}
Array and function types undergo decay during construction, as std::any uses std::decay_t:
char buffer[12] = "benchmark";
std::any wrapped(buffer);
char* extracted = std::any_cast<char*>(wrapped); // "benchmark"
A comprehensive example:
#include <any>
#include <iostream>
#include <string>
#include <type_traits>
#include <utility>
int main() {
// Basic usage
std::any sample1 = 256;
std::cout << "1) sample1 contains int: "
<< std::any_cast<int>(sample1) << '\n';
try {
auto txt = std::any_cast<std::string>(sample1); // throws
}
catch (const std::bad_any_cast& e) {
std::cout << "2) " << e.what() << '\n';
}
// Safe pointer-based checking
if (int* i = std::any_cast<int>(&sample1))
std::cout << "3) sample1 is int: " << *i << '\n';
else if (std::string* s = std::any_cast<std::string>(&sample1))
std::cout << "3) sample1 is string: " << *s << '\n';
else
std::cout << "3) sample1 holds unknown type\n";
// Reference modification
sample1 = std::string("modified");
auto& ref = std::any_cast<std::string&>(sample1);
ref[0] = 'M';
std::cout << "4) sample1: "
<< std::any_cast<std::string const&>(sample1) << '\n';
// Rvalue reference for move semantics
auto str = std::any_cast<std::string&&>(std::move(sample1));
static_assert(std::is_same_v<decltype(str), std::string>);
// Original any is in valid but unspecified state
std::cout << "5) original size: "
<< std::any_cast<std::string>(&sample1)->size() << '\n'
<< "6) moved value: " << str << '\n';
}
Output:
1) sample1 contains int: 256
2) bad any_cast
3) sample1 is int: 256
4) sample1: Modified
5) original size: 0
6) moved value: Modified
Modifier Functions
emplace
Destroys current content and constructs a new object in place:
#include <algorithm>
#include <any>
#include <iostream>
#include <string>
#include <vector>
struct CelestialBody {
std::string designation;
int catalogNumber;
CelestialBody(std::string name, int id)
: designation(std::move(name)), catalogNumber(id) {
std::cout << "CelestialBody::CelestialBody(string, int)\n";
}
void describe() const {
std::cout << "CelestialBody{ \"" << designation
<< "\" : " << catalogNumber << " }\n";
}
};
int main() {
std::any celestial;
celestial.emplace<CelestialBody>("Sirius", 2845);
const auto* body = std::any_cast<CelestialBody>(&celestial);
body->describe();
std::any storage;
storage.emplace<std::vector<char>>({ 'C', '+', '+', '1', '7' });
const auto* vec = std::any_cast<std::vector<char>>(&storage);
std::for_each(vec->cbegin(), vec->cend(),
[](char c) { std::cout << c; });
std::cout << '\n';
}
Output:
CelestialBody::CelestialBody(string, int)
CelestialBody{ "Sirius" : 2845 }
C++17
reset
Destroys contained object, leaving the any empty.
swap
Exchanges contents between two std::any instances.
Observer Functions
has_value
Returns true if the any contains a value:
#include <any>
#include <iostream>
#include <string>
int main() {
std::boolalpha(std::cout);
std::any empty;
std::cout << "empty.has_value(): " << empty.has_value() << "\n";
std::any num = 42;
std::cout << "num.has_value(): " << num.has_value() << '\n';
std::cout << "num = " << std::any_cast<int>(num) << '\n';
num.reset();
std::cout << "after reset, has_value(): " << num.has_value() << '\n';
auto txt = std::make_any<std::string>("Andromeda");
std::cout << "txt.has_value(): " << txt.has_value() << '\n';
std::cout << "txt = \"" << std::any_cast<std::string&>(txt) << "\"\n";
txt.reset();
std::cout << "after reset, has_value(): " << txt.has_value() << '\n';
}
Output:
empty.has_value(): false
num.has_value(): true
num = 42
after reset, has_value(): false
txt.has_value(): true
txt = "Andromeda"
after reset, has_value(): false
type
Returns std::type_info of contained type, or typeid(void) if empty:
#include <type_traits>
#include <any>
#include <functional>
#include <iomanip>
#include <iostream>
#include <typeindex>
#include <typeinfo>
#include <unordered_map>
#include <vector>
template<class T, class F>
inline std::pair<const std::type_index, std::function<void(std::any const&)>>
make_handler(F const& f) {
return {
std::type_index(typeid(T)),
[g = f](std::any const& a) {
if constexpr (std::is_void_v<T>)
g();
else
g(std::any_cast<T const&>(a));
}
};
}
static std::unordered_map<
std::type_index, std::function<void(std::any const&)>>
dispatcher {
make_handler<void>([]{ std::cout << "{empty}"; }),
make_handler<int>([](int x){ std::cout << x; }),
make_handler<unsigned>([](unsigned x){ std::cout << x; }),
make_handler<float>([](float x){ std::cout << x; }),
make_handler<double>([](double x){ std::cout << x; }),
make_handler<char const*>([](char const* s)
{ std::cout << std::quoted(s); }),
};
inline void dispatch(const std::any& a) {
if (const auto it = dispatcher.find(std::type_index(a.type()));
it != dispatcher.cend()) {
it->second(a);
} else {
std::cout << "Unhandled type " << std::quoted(a.type().name());
}
}
template<class T, class F>
inline void register_handler(F const& f) {
std::cout << "Registered handler for "
<< std::quoted(typeid(T).name()) << '\n';
dispatcher.insert(make_handler<T>(f));
}
int main() {
std::vector<std::any> collection { {}, 100, 255u, 2.71828f,
1.41421, "π≈3.14159" };
std::cout << "Collection contents: { ";
for (const std::any& item : collection) {
dispatch(item);
std::cout << ", ";
}
std::cout << "}\n";
dispatch(std::any(0xCAFEULL)); // unregistered type
std::cout << '\n';
register_handler<unsigned long long>([](auto x) {
std::cout << std::hex << std::showbase << x;
});
dispatch(std::any(0xCAFEULL)); // now registered: 0xcafe
std::cout << '\n';
}
Output:
Collection contents: { {empty}, 100, 255, 2.71828, 1.41421, "π≈3.14159", }
Unhandled type "m"
Registered handler for "m"
0xcafe