[2026] C++ any | Type Erasing Guide
이 글의 핵심
A guide that summarizes std::any and variant·void* comparison, type safety, any_cast, practical examples, and performance overhead.
What is any?
std::any is a type erasure container introduced in C++17. You can store values of any type, and you can check the type and extract the value at runtime. A type-safe alternative to void*.
아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <any>
// 실행 예제
std::any a = 42; // int
a = 3.14; // double
a = std::string{"hello"}; // string
// Access after checking the type
if (a.type() == typeid(std::string)) {
std::cout << std::any_cast<std::string>(a) << std::endl;
}
Why do you need it?:
- Type Flexibility: When the type is unknown at compile time.
- Type safety: Safer type erasure than
void*. - Automatic Management: Automatic life cycle management
- Exception safety: Exception when accessing the wrong type. 아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ void*: type unsafe, manual management
void* ptr = new int(42);
int x = *static_cast<int*>(ptr); // No type checking
delete ptr; // manual deletion
// ✅ std::any: type safe, automatically managed
std::any a = 42;
if (a.type() == typeid(int)) {
int x = std::any_cast<int>(a); // Check type
}
// automatic destruction
How any works:
std::any stores type information and value together internally. Small objects are stored on the stack and large objects are stored on the heap (Small Object Optimization).
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// Conceptual Implementation
// 타입 정의
class any {
void* data_;
const std::type_info* type_;
void (*deleter_)(void*);
public:
template<typename T>
any(T value) {
data_ = new T(std::move(value));
type_ = &typeid(T);
deleter_ = [](void* p) { delete static_cast<T*>(p); };
}
~any() {
if (data_) {
deleter_(data_);
}
}
const std::type_info& type() const {
return *type_;
}
};
any vs variant vs optional:
| Features | std::any | std::variant | std::optional |
|---|---|---|---|
| Storage type | All types | fixed type | Single type |
| type tracking | runtime | compile time | N/A |
| memory | heap (large object) | stack | stack |
| Performance | slow | Fast | Very fast |
| Type Safe | runtime | compile time | compile time |
| Use | Plugins, Settings | state machine, error | null alternative |
| 아래 코드는 cpp를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다. |
// any: Any type possible
std::any a = 42;
a = std::string{"hello"};
a = std::vector<int>{1, 2, 3};
// variant: only certain types
std::variant<int, std::string> v = 42;
v = std::string{"hello"};
// v = std::vector<int>{}; // error
// optional: single type
std::optional<int> opt = 42;
opt = std::nullopt;
any vs variant vs void* In-depth
void*
- Advantages: Any address can be stored, and integration with the C API seems simple.
- Disadvantage: Ownership/Lifetime/Actual Type depends only on code contract. An invalid
static_castis undefined behavior, and unpaireddeleteis immediately UB. - Summary: Although still used for opaque buffers with fixed layouts (e.g. FFI buffers),
std::anyor smart pointer+interfaces are safe for holding owned objects. std::variant - If a closed set (e.g.
int | double | string) is fixed at compile time,visit·holds_alternativewill allow most mistakes to be caught at the compile stage. - Memory is usually placed in fixed size buffers and often operates without heap allocation (implementation dependent). std::any
- Suitable when a set of storage types is open and unknown types can enter, such as plugins.
- In return, type checking goes to runtime, and bad
any_castis an exception (bad_any_cast). Selection guide one line: variant if the type candidate is known,void*(oruintptr_t) if only a heap pointer is passed and the contract is clear, and any if “value ownership + arbitrary type” in between.
Type safety
The safety of std::any is less about “Does it prevent bad casts?” and more about “Can bad casts be detected at runtime?”
any_cast<T>(a)must have an internal storage type that exactly matchesT(there are reference·cv qualifier rules). If you try to insertintand take it out withlong, it will fail.type()returnstype_info, so theif (a.type() == typeid(Foo))pattern is possible, but maintenance is better left toany_castat once.- At interface boundaries, it is safe to limit types to
variantor dedicated base classes whenever possible, and to keepanyonly in layers that really need heterogeneity.
any_cast Completely organized
Copy by value
std::any a = std::string{"hi"};
std::string s = std::any_cast<std::string>(a); // copy
Edited with reference
std::any a = 10;
std::any_cast<int&>(a) = 20;
Pointer overload (nullptr on failure) 아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// 실행 예제
std::any a = 3.14;
if (double* p = std::any_cast<double>(&a)) {
*p = 2.71;
}
any_cast throws bad_any_cast on value/reference overloads if the stored type and the requested type do not match. Pointer overloads return nullptr instead of an exception, so the hot path has the option of branching to pointer form.
Move-only type
Types that cannot be copied or can only be moved are entered with std::make_any<T>(...), and when taken out, use the std::any_cast<T&>(a) or any_cast<T>(std::move(a)) pattern according to the type.
Performance overhead
Approximate cost factors are as follows:
- Type information:
type_infosearch, internal comparison whenany_cast. - Storage method: Depending on the implementation, small objects are placed in an inline buffer with Small Object Optimization, but large objects or complex types may be subject to heap allocation.
- Copy/Move: If you frequently copy
anyitself, the cost of copying the stored value is also incurred. mitigation
- If the candidate type is determined, move it to std::variant.
- If you repeatedly cast from the same scope, either cast only once and get a reference, or get it as a
variant/concrete typein the first place. - In configuration maps, etc., check whether a strongly typed struct or
toml/jsonparser result type is better than astringkey +anyvalue.
Summary of practical use
| Situation | Why any is suitable |
|---|---|
| Script/plugin passes arbitrary type payload | Difficult to enumerate types in advance |
| Configuration file values are mixed, such as int/string/bool, etc. | Convenient as a simple key-value store (however, if the schema grows, consider a dedicated type) |
| Various payloads on event bus | Branch to any_cast in handler |
| Test mock object/mock dependency | Used only to a limited extent |
Conversely, if the type is fixed, such as numerical hot loop, real-time audio sample processing, or internal API, it is better to avoid any and use variant or a direct type. |
Default use
아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <any>
// generation
std::any a1 = 42;
std::any a2 = std::string{"hello"};
std::any a3; // empty any
// check
if (a1.has_value()) {
std::cout << "has value" << std::endl;
}
// type
std::cout << a1.type().name() << std::endl;
Practical example
Example 1: Heterogeneous containers
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <any>
#include <vector>
#include <string>
int main() {
std::vector<std::any> data;
data.push_back(42);
data.push_back(3.14);
data.push_back(std::string{"hello"});
for (const auto& item : data) {
if (item.type() == typeid(int)) {
std::cout << "int: " << std::any_cast<int>(item) << std::endl;
} else if (item.type() == typeid(double)) {
std::cout << "double: " << std::any_cast<double>(item) << std::endl;
} else if (item.type() == typeid(std::string)) {
std::cout << "string: " << std::any_cast<std::string>(item) << std::endl;
}
}
}
Example 2: Configuration Repository
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <any>
#include <map>
#include <string>
class Config {
std::map<std::string, std::any> settings;
public:
template<typename T>
void set(const std::string& key, const T& value) {
settings[key] = value;
}
template<typename T>
T get(const std::string& key) const {
auto it = settings.find(key);
if (it != settings.end()) {
return std::any_cast<T>(it->second);
}
throw std::runtime_error("No key");
}
};
int main() {
Config config;
config.set("port", 8080);
config.set("host", std::string{"localhost"});
config.set("timeout", 30.0);
int port = config.get<int>("port");
std::string host = config.get<std::string>("host");
double timeout = config.get<double>("timeout");
}
Example 3: Event system
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <any>
#include <functional>
#include <map>
#include <string>
class EventBus {
std::map<std::string, std::vector<std::function<void(std::any)>>> handlers;
public:
void on(const std::string& event, std::function<void(std::any)> handler) {
handlers[event].push_back(handler);
}
void emit(const std::string& event, std::any data) {
if (auto it = handlers.find(event); it != handlers.end()) {
for (auto& handler : it->second) {
handler(data);
}
}
}
};
int main() {
EventBus bus;
bus.on("message", [](std::any data) {
auto msg = std::any_cast<std::string>(data);
std::cout << "Message: " << msg << std::endl;
});
bus.on("count", [](std::any data) {
auto count = std::any_cast<int>(data);
std::cout << "Count: " << count << std::endl;
});
bus.emit("message", std::string{"Hello"});
bus.emit("count", 42);
}
Example 4: Type-safe wrapper
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <any>
class SafeAny {
std::any data;
public:
template<typename T>
void set(const T& value) {
data = value;
}
template<typename T>
std::optional<T> get() const {
try {
return std::any_cast<T>(data);
} catch (const std::bad_any_cast&) {
return std::nullopt;
}
}
bool empty() const {
return !data.has_value();
}
};
int main() {
SafeAny sa;
sa.set(42);
if (auto val = sa.get<int>()) {
std::cout << "Value: " << *val << std::endl;
}
if (auto val = sa.get<double>()) {
std::cout << "double" << std::endl;
} else {
std::cout << "Type mismatch" << std::endl;
}
}
Accessing value
아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
std::any a = 42;
// any_cast: value
int x = std::any_cast<int>(a);
// any_cast: pointer
if (int* ptr = std::any_cast<int>(&a)) {
std::cout << *ptr << std::endl;
}
// any_cast: reference
int& ref = std::any_cast<int&>(a);
Frequently occurring problems
Problem 1: Type mismatch
아래 코드는 cpp를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
std::any a = 42;
// ❌ Wrong type
try {
double d = std::any_cast<double>(a); // std::bad_any_cast
} catch (const std::bad_any_cast&) {
std::cout << "Type mismatch" << std::endl;
}
// ✅ Access after confirmation
if (a.type() == typeid(int)) {
int x = std::any_cast<int>(a);
}
Issue 2: References
아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
std::any a = 42;
// ❌ Copy
int x = std::any_cast<int>(a);
// ✅ See also
int& ref = std::any_cast<int&>(a);
ref = 100;
std::cout << std::any_cast<int>(a) << std::endl; // 100
Issue 3: Performance
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// any has overhead
// - Save type information
// - Dynamic allocation (large objects)
// - Type check
// ✅ Alternative: variant (if type is known)
std::variant<int, double, std::string> v;
Problem 4: Copy
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
struct NonCopyable {
NonCopyable() = default;
NonCopyable(const NonCopyable&) = delete;
};
// ❌ Non-copyable type
// std::any a = NonCopyable{}; // error
// ✅ Move
std::any a = std::make_any<NonCopyable>();
any vs variant
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// any: any type (runtime)
std::any a = 42;
a = std::string{"hello"};
// variant: defined type (compile time)
std::variant<int, std::string> v = 42;
v = std::string{"hello"};
// variant recommended (if type is known)
Practice pattern
Pattern 1: Plugin system
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class Plugin {
public:
virtual ~Plugin() = default;
virtual std::string getName() const = 0;
virtual std::any execute(const std::any& input) = 0;
};
class PluginManager {
std::map<std::string, std::unique_ptr<Plugin>> plugins_;
public:
void registerPlugin(std::unique_ptr<Plugin> plugin) {
plugins_[plugin->getName()] = std::move(plugin);
}
std::any execute(const std::string& name, const std::any& input) {
if (auto it = plugins_.find(name); it != plugins_.end()) {
return it->second->execute(input);
}
throw std::runtime_error("No plugin found");
}
};
// Plugin implementation
class CalculatorPlugin : public Plugin {
public:
std::string getName() const override {
return "calculator";
}
std::any execute(const std::any& input) override {
auto values = std::any_cast<std::vector<int>>(input);
int sum = 0;
for (int v : values) {
sum += v;
}
return sum;
}
};
Pattern 2: Type-safe message bus
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class MessageBus {
struct Handler {
std::function<void(const std::any&)> callback;
std::type_index expectedType;
};
std::map<std::string, std::vector<Handler>> handlers_;
public:
template<typename T>
void subscribe(const std::string& topic, std::function<void(const T&)> callback) {
handlers_[topic].push_back({
[callback](const std::any& data) {
callback(std::any_cast<const T&>(data));
},
std::type_index(typeid(T))
});
}
template<typename T>
void publish(const std::string& topic, const T& data) {
if (auto it = handlers_.find(topic); it != handlers_.end()) {
for (auto& handler : it->second) {
if (handler.expectedType == std::type_index(typeid(T))) {
try {
handler.callback(std::any(data));
} catch (const std::bad_any_cast& e) {
std::cerr << "Type mismatch: " << e.what() << '\n';
}
}
}
}
}
};
// use
MessageBus bus;
bus.subscribe<std::string>("log", [](const std::string& msg) {
std::cout << "log: " << msg << '\n';
});
bus.publish("log", std::string{"Hello"});
Pattern 3: Dynamic property system
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class Entity {
std::map<std::string, std::any> properties_;
public:
template<typename T>
void setProperty(const std::string& name, const T& value) {
properties_[name] = value;
}
template<typename T>
std::optional<T> getProperty(const std::string& name) const {
auto it = properties_.find(name);
if (it == properties_.end()) {
return std::nullopt;
}
try {
return std::any_cast<T>(it->second);
} catch (const std::bad_any_cast&) {
return std::nullopt;
}
}
bool hasProperty(const std::string& name) const {
return properties_.count(name) > 0;
}
};
// use
Entity player;
player.setProperty("health", 100);
player.setProperty("name", std::string{"Hero"});
player.setProperty("position", std::vector<double>{10.0, 20.0});
if (auto health = player.getProperty<int>("health")) {
std::cout << "Health: " << *health << '\n';
}
FAQ
Q1: What is any?
A: A type erasure container in C++17 that can store values of any type. Check the type and extract the value at runtime.
std::any a = 42;
a = 3.14;
a = std::string{"hello"};
Q2: Where is any used?
A:
- Heterogeneous containers: Store various types in one container
- Plugin system: Passing data between plugins
- Settings Storage: Stores various types of setting values
- Event System: Various types of event data 다음은 간단한 cpp 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
std::vector<std::any> data;
data.push_back(42);
data.push_back(3.14);
data.push_back(std::string{"hello"});
Q3: How do I access the values?
A: Use std::any_cast. In case of type mismatch, an exception is thrown. 아래 코드는 cpp를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
std::any a = 42;
// Extract value (exceptions possible)
int x = std::any_cast<int>(a);
// Pointer extraction (safe)
if (int* ptr = std::any_cast<int>(&a)) {
std::cout << *ptr << '\n';
}
// Reference extraction
int& ref = std::any_cast<int&>(a);
Q4: What is the performance of any?
A: There is overhead. There are costs of storing type information, dynamic allocation (large objects), and type checking. 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// any: overhead
std::any a = 42; // Type information + value storage
// variant: faster (if type is known)
std::variant<int, double, std::string> v = 42;
Recommended: Use variant if type is known in advance
Q5: What is the difference from variant?
A:
- any: Any type possible, runtime type check, slow
- variant: only defined types, compile-time type check, fast 아래 코드는 cpp를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// any: any type
std::any a = 42;
a = std::vector<int>{1, 2, 3}; // OK
// variant: only certain types
std::variant<int, double> v = 42;
// v = std::vector<int>{}; // error
Selection criteria:
- If the type is known in advance: variant
- If the type is not known in advance: any
Q6: Can any store a reference?
A: Not possible directly, but you can use std::reference_wrapper.
아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
int x = 42;
// ❌ Cannot save reference
// std::any a{x}; // copied
// ✅ Use reference_wrapper
std::any a = std::ref(x);
std::reference_wrapper<int> ref = std::any_cast<std::reference_wrapper<int>>(a);
ref.get() = 100;
std::cout << x << '\n'; // 100
Q7: What is the memory allocation for any?
A: Uses Small Object Optimization (SOO). Small objects are stored on the stack and large objects are stored on the heap. 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// Small objects: stack (usually no more than 16-32 bytes)
std::any a1 = 42; // stack
// Large objects: heap
std::any a2 = std::vector<int>(1000); // hip
Q8: Any learning resources?
A:
- “C++17 The Complete Guide” by Nicolai Josuttis
- “Effective Modern C++” by Scott Meyers
- cppreference.com - std::any
Related posts: variant, optional, type_erasure.
One-line summary:
std::anyis a C++17 type-erasing container that can store any type.
Good article to read together (internal link)
Here’s another article related to this topic.
- C++ Type Erasure | “Type erasure” pattern guide
- C++ optional | “Optional Values” Guide
- C++ variant | “Type-safe union” guide
Practical tips
These are tips that can be applied right away in practice.
Debugging tips
- If you run into a problem, check the compiler warnings first.
- Reproduce the problem with a simple test case
Performance Tips
- Don’t optimize without profiling
- Set measurable indicators first
Code review tips
- Check in advance for areas that are frequently pointed out in code reviews.
- Follow your team’s coding conventions
Practical checklist
This is what you need to check when applying this concept in practice.
Before writing code
- Is this technique the best way to solve the current problem?
- Can team members understand and maintain this code?
- Does it meet the performance requirements?
Writing code
- Have you resolved all compiler warnings?
- Have you considered edge cases?
- Is error handling appropriate?
When reviewing code
- Is the intent of the code clear?
- Are there enough test cases?
- Is it documented? Use this checklist to reduce mistakes and improve code quality.