C++17 introduces a new template class any. This class std::any in C++ is like a container and it saves a variable or object of any type. Generally, C++ language is very strict about the type. This is because, C++ defines rules about what operations are valid for particular type and what are not valid. Therefore, std::any class looks like an exception, but in reality this is not. The rules of contained object’s type remains very much relevant. This is explained below.
Internally, the class any stores both the data as well as the datatype as typeid. The compiler generates code to use required run-time checks with its stored type. This is important when the function std::any_cast< > converts the object to a value at run-time. When the program converts the internally stored object to a wrong type, then exception happens.
Simple example of std::any & std::any_cast
#include <iostream> //main header
#include <any> //for std any
using namespace std;//namespace
int main()
{
std::any ax;
ax = 4;
cout << "ax = " << std::any_cast<int>(ax) << endl;
cout << "ax = " << std::any_cast<char>(ax) << endl;
return 0;
}
The program throws exception bad alloc because any_cast<char> is converting an integer to a char type.
Declaration of any variable
The declaration of any can be either an empty container or a container with a specific value.
Following 2 declarations are valid
std::any ax; // x is empty container
std::any ay = 4; // y is container with value 4 and typeid=int
Changing contained value with different type of value
The class any has assignment operator. This operator can substitute a contained value with a completely different value of different type.
The following example, demonstrates, how to change the stored value to “char” from an “int”.
#include <iostream> //main header
#include <any> //for any
using namespace std;//namespace
int main()
{
std::any ax; //ax is Empty
ax = 4; //ax has Integer
cout << "ax = " << std::any_cast<int>(ax) << endl;
ax = 'a'; //ax has char
cout << "ax = " << std::any_cast<char>(ax) << endl;
return 0;
}
The output is:
ax = 4 ax = a
Move semantics for std::any
Although, std::any supports move operations, but directly casting the contained value to R-value type is not valid. In following example, the any object “x” was constructed using R-value from move operation. However, it cannot cast the contained integer to “int&&” R-value type.
#include <iostream> //main header
#include <any> //for any
using namespace std;//namespace
int main()
{
int ii = 5;
std::any ax = std::move(ii); //Construct with R-value
int &&ay= std::any_cast<int&&>(ax); //Error cast R-value
return 0;
}
The compiler generates following error message due to any_cast<int&&>.
Checking the contained value
The class std::any in C++ has a member function “has_value( )” to check if the container has any value or not. This function returns true or false.
This class also has a function “reset” which relinquish the contained type and becomes an empty container.
#include <iostream> //main header
#include <any> //for any
using namespace std; //namespace
void checkValue(std::any &ax)
{
if(ax.has_value())
cout << "ax has value" << endl;
else
cout << "ax is empty" << endl;
}
int main()
{
std::any ax; // ax is empty initially
checkValue(ax);
ax = 4; // ax is now integer
cout << "ax = " << std::any_cast<int>(ax) << endl;
checkValue(ax);
ax.reset(); // ax is again empty
checkValue(ax);
return 0;
}
ax is empty
ax = 4
ax has value
ax is empty
Checking the contained type
The class has a member function to get the contained objects’ type. This returns std::type_info object. The following example shows the usage.
#include <iostream> //main header
#include <any> //for any
using namespace std;//namespace
int main()
{
std::any ax;
ax = 4;
cout << ax.type().name() << endl;
return 0;
}
The output is:
i
Using type_info object to compare
The type_info object contains comparison operator. This operator enables the program to compare 2 type_info objects and returns the result in boolean value. Therefore, to check the actual type, the program may compare them directly using typeid function. The typeid function also returns std::type_info object.
In the following example, the return values from type( ) and from typeid( ) are used.
#include <iostream> //main header
#include <any> //for any
using namespace std;//namespace
int main()
{
std::any ax = 8;
if(ax.type() == typeid(int))
cout << "this is int" << endl;
return 0;
}
Output:
this is int
Using decltype with std::any
The decltype returns the actual type of any object. Same is true for std::any.
The following program demonstrates the use of decltype.
#include <iostream> //main header
#include <any> //for any
using namespace std; //namespace
int main()
{
std::any ax = 8;
int ay = 9;
cout << typeid(decltype(ax)).name() << endl;
cout << typeid(decltype(ay)).name() << endl;
return 0;
}
Output:
St3any
i
Convenience function make_any
make_any is a template function which creates an object of type std::any. This function takes the type as template parameter and the arguments to initialize the contained value.
#include <iostream> //main header
#include <any> //for any
using namespace std; //namespace
int main()
{
auto ax = std::make_any<int>(8);
auto ay = std::make_any<float>(9.2);
cout << std::any_cast<int>(ax) << endl;
cout << std::any_cast<float>(ay) << endl;
return 0;
}
8
9.2
Please note, it can accept multiple arguments to intialize the contained value.
class RationalNumber
{
int inum;
int iden;
public:
RationalNumber(int in, int id) : inum(in), iden(id)
{}
};
auto val = std::make_any<RationalNumber>(3,4); //2 arguments
Construct an object with emplace.
Creation of object for storing in std::any will involve creation of a temporary object as first step. This temporary will then use copy constructor to make an actual object for the container. Finally, the compiler will destroy the temporary object.
Fortunately, the class std::any has emplace member function. This function will accept the constructor arguments and then creates an object directly inside the container. Internally, it uses move semantics to achieve the desired result.
The following program creates 3 any objects:
- First using a parameterized constructor & emplace
- Second using a temporary object and copy constructor
- Third, using default constructor and emplace
#include <iostream> //main header
#include <any> //for any
using namespace std;//namespace
class MainFunda
{
public:
MainFunda() {
cout << "Default constructor" << endl;
}
MainFunda(const MainFunda&) {
cout << "Copy constructor" << endl;
}
MainFunda(int i) {
cout << "Parametrized constructor" << endl;
}
~MainFunda() {
cout << "Destructor" << endl;
}
};
int main()
{
std::any ax;
ax.emplace<MainFunda>(2);
// Calls parameterized constructor
std::any ay = MainFunda();
// Calls default constructor & copy constructor
std::any az;
az.emplace<MainFunda>();
// Calls default constructor
return 0;
}
Output
Parametrized constructor
Default constructor
Copy constructor
Destructor
Default constructor
Destructor
Destructor
Destructor