std::any in C++

Share the Article

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.

terminate called with std bad_any_cast in C++17

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&&>.

compiler error any_cast to R-value reference in C++17

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:

  1. First using a parameterized constructor & emplace
  2. Second using a temporary object and copy constructor
  3. 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

Main Funda: std::any in C++ is a special container which stores any kind of object and also information about its type

Related Topics:

What are dependent scope type in templates?
What is an implicit interface? 
Calling member functions in template base class
What is template meta programming (TMP)
How delete keyword can be used to filter polymorphism
Concept of Inline Functions
What is reference collapsing?
How std::forward( ) works?
How std::move() function works?
Smart Pointers: shared_ptr <T> 
Smart Pointers: unique_ptr<T>
What is move constructor ?
Understanding Constant Variables

Share the Article

Leave a Reply

Your email address will not be published. Required fields are marked *