std::any
is a feature of the C++ standard library that was introduced in C++17.
This component belongs to the set of type-safe container classes, providing a safe means to store and manipulate values of any type.
It is especially useful when you need to deal with situations where the type of the variable can vary! 😃
Then you say:
- Oh man! Good. For these cases I use
void *
.
Yes, you’re really right, but have you seen how the new generation is in relation to memory safety???
Not to mention that void*
is really dangerous!
If you do this, it works:
void * some_data; // Bad idea
std::string str = "Hi";
int x = 3;
decltype(x) y = 6;
some_data = &str;
std::cout << *(std::string*)some_data << '\n';
some_data = &x;
std::cout << *(int*)some_data << '\n';
some_data = &y;
std::cout << "Type of y: " << typeid(y).name() << '\n'; // include typeinfo
But, the chance of this giving s%1t
is great! At the end of using these variables, some_data
will continue to exist, that is, an indefinite lifetime!
And it is to replace void*
that std::any
was created in Modern C++ which, of course, is totally Safe!
In other words, it is a wrapper that encapsulates your variable to a shared_ptr
(smart pointers) of life! Yes, and there is even a std::make_any
!!!
std::any
First you need to include its header:
Logically, it only works from C++17 as was said at the beginning!
#include <any>
And now the same code that was presented above, but using std::any
:
#include <iostream>
#include <any>
int main(){
std::any some_data;
std::string str = "Hi";
int x = 3;
auto y = std::make_any<decltype(x)>(6);
some_data = str;
std::cout << std::any_cast<std::string>(some_data) << '\n';
some_data = x;
std::cout << std::any_cast<int>(some_data) << '\n';
some_data = y;
std::cout << "Type of y: " << some_data.type().name() << '\n';
}
In the code above we saw that:
std::any some_data;
- Declares the variable;std::any_cast<T>(some_data)
- Converts to the desired type;std::make_any<T>
- Another way to create objects;some_data.type().name()
- Gets the data type without needing typeinfo
.And you can use it for absolutely everything: std::vector
, Lambda and all existing data types!
And the guy asks something else:
- OK! What if I want to end the lifetime of
std::any
manually?
Just use the reset
union structure or even the initialization operator:
some_data.reset();
// Or
some_data = {};
— And to check if
std::any
is empty?
Use has_value()
:
std::cout << (some_data.has_value() ? "Full!" : "Empty.") << '\n';
The unionless type()
with name()
can be used to compare types:
std::cout << (some_data.type() == typeid(void)) << '\n'; // 0 to false
std::cout << (some_data.type() == typeid(int)) << '\n'; // 1 to true
To use Boolean names use:
std::cout << std::boolalpha << (some_data.type() == typeid(int)) << '\n';
.
To throw exceptions you must use std::bad_any_cast
:
try {
std::any any_str("Hiii");
auto my_any{ std::make_any<std::string>(any_str.type().name()) };
std::cout << std::any_cast<std::string>(my_any) << '\n';
}catch (const std::bad_any_cast& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
To check whether everything really complies, never forget to use the flags for your compiler: -Wall -Wextra -pedantic -g -fsanitize=address
.
Imagine you have code that needs to concatenate several types and return a string. However, one of the types can be: int, double or std::string.
If you use std::any_cast<T>
in the return like this:
Example:
#include <iostream>
#include <any>
#include <sstream>
enum class Message {
SUCCESS,
WARNING,
ERROR,
UNKNOW
};
std::string add_info(Message, const std::string&, std::any, int);
int main(){
std::any obj;
obj = std::string("Start");
std::cout << add_info(Message::SUCCESS, " of type string: ", obj, 3) << '\n';
obj = 6;
std::cout << add_info(Message::WARNING, " of type int: ", obj, 9) << '\n';
obj = 3.14;
std::cout << add_info(Message::ERROR, " of type double: ", obj, 0) << '\n';
obj.reset();
std::cout << add_info(Message::UNKNOW, " no type: ", obj, 9) << '\n';
obj = "CONST_CHAR";
std::cout << add_info(Message::SUCCESS, " no type: ", nullptr, 9) << '\n';
return 0;
}
std::string add_info(Message msg, const std::string& out, std::any object, int num){
return std::any_cast<std::string>(msg) + out + "'" + std::any_cast<std::string>(object) + "' " + std::to_string(num);
}
Compile:
g++ -Wall -Wextra -pedantic -g -fsanitize=address main.cpp
.
There will be a std::bad_any_cast
in the output:
terminate called after throwing an instance of 'std::bad_any_cast'
what(): bad any_cast
Aborted
Both conversions (from msg
and object
) are incorrect:
std::any_cast<std::string>(msg) + ...
// How much
std::any_cast<std::string>(object)
You need to make a switch case
for the enumerator and in the case of the object
parameter: You will need to use has_value()
, store the type()
in std::type_info&
and use a std:: stringstream
to assign return types with union to: str()
, like this:
#include <iostream>
#include <any>
#include <sstream>
enum class Message {
SUCCESS,
WARNING,
ERROR,
UNKNOW
};
std::string add_info(Message, const std::string&, std::any, int);
int main(){
std::any obj;
obj = std::string("Start");
std::cout << add_info(Message::SUCCESS, " of type string: ", obj, 3) << '\n';
obj = 6;
std::cout << add_info(Message::WARNING, " of type int: ", obj, 9) << '\n';
obj = 3.14;
std::cout << add_info(Message::ERROR, " of type double: ", obj, 0) << '\n';
obj.reset();
std::cout << add_info(Message::UNKNOW, " no type: ", obj, 9) << '\n';
obj = "CONST_CHAR";
std::cout << add_info(Message::SUCCESS, " no type: ", nullptr, 9) << '\n';
return 0;
}
std::string add_info(Message msg, const std::string& out, std::any object, int num){
std::string local_msg {"NOTHING"};
std::stringstream ss;
switch (msg){
case Message::SUCCESS:
local_msg = "SUCCESS";
break;
case Message::WARNING:
local_msg = "WARNING";
break;
case Message::ERROR:
local_msg = "ERROR";
break;
case Message::UNKNOW:
local_msg = "UNKNOW";
break;
}
if (object.has_value()) {
const std::type_info& type = object.type();
if (type == typeid(std::string)) {
ss << std::any_cast<std::string>(object);
} else if (type == typeid(int)) {
ss << std::any_cast<int>(object);
} else if (type == typeid(double)) {
ss << std::any_cast<double>(object);
} else {
ss << "null";
}
} else {
ss << "[no object]";
}
return local_msg + out + "'" + ss.str() + "' " + std::to_string(num);
}
Compile: g++ -Wall -Wextra -pedantic -g -fsanitize=address main.cpp
and after running ./a.out
, the output will be:
SUCCESS of type string: 'Start' 3
WARNING of type int: '6' 9
ERROR of type double: '3.14' 0
UNKNOW without type: '[no object]' 9
SUCCESS without type: 'null' 9
It seems laborious, but this is the correct way to end the lifespan of any type!
In addition to being completely SAFE, std::any
is very practical and a great help!
For more information visit: https://en.cppreference.com/w/cpp/utility/any