Lei Mao bio photo

Lei Mao

Machine Learning, Artificial Intelligence, Computer Science.

Twitter Facebook LinkedIn GitHub   G. Scholar E-Mail RSS

Introduction

Due to the dynamic typing nature of Python, Python’s dict serves as a good container for passing a variety of values of different types to functions. The key of dict could be values which are hashable, and the value of dict could be values of any type.


C++, however, is a static typing programming language. Although it also has hash table based data structure, such as unordered_map and map, the conventional usages of the hash table data structure are very limited. The variable typse need to be determined during compile time, and by default, there should be only one type for the key values and the value values respectively, which makes the C++ “dictionary” less flexible to use.


C++ standard 17 introduces a new type std::any serving as a type-safe container for single values of any type, as long as the value is copiable. This std::any would make the usage of C++ dictionary more flexible.


In this blog post, I am going to talk about how to create C++ dictionary with std::any as the type for the stored values.

Examples

Personal Informatoin

We would like to implement a dictionary for Michael’s personal informations. In Python we would use dict and in C++ we would use unordered_map.

Python Implementation

The code below is simple and self-explanatory.

class BookCollections(object):

    def __init__(self, books):
        self.books = books

def main():

    personal_information = dict()

    name = "Michael"
    age = 20
    credit_history = [10,20,30]
    id = "US123456"
    books = BookCollections(books=["Chess", "Go"])

    personal_information["name"] = name
    personal_information["age"] = age
    personal_information["credit_history"] = credit_history
    personal_information["id"] = id
    personal_information["books"] = books

    retrieved_name = personal_information["name"]
    retrieved_age = personal_information["age"]
    retrieved_credit_history = personal_information["credit_history"]
    retrieved_id = personal_information["id"]
    retrieved_books = personal_information["books"]

    if ((name != retrieved_name) or (age != retrieved_age) or (credit_history != retrieved_credit_history) or (id != retrieved_id) or (books != retrieved_books)):
        print("Dictionary Failed!")
    else:
        print("Dictionary Worked!")
        
if __name__ == "__main__":
    
    main()

We ran the Python program and got the following result:

$ python python_dict.py 
Dictionary Worked!

C++ Implementation

With a dictionary of type std::unordered_map<std::string, std::any>, the stored value could be any type.

#include <unordered_map>
#include <string>
#include <any>
#include <iostream>
#include <vector>

class BookCollections
{
public:
    BookCollections()
    {
    }
    BookCollections(std::vector<std::string> books)
    {
        mBooks = books;
    }
    std::vector<std::string> getBooks() const
    {
        return this->mBooks;
    }
    bool operator == (const BookCollections& books)
    {
        if (this->mBooks == books.getBooks())
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    bool operator != (const BookCollections& books)
    {
        if (this->mBooks != books.getBooks())
        {
            return true;
        }
        else
        {
            return false;
        }
    }
private:
    std::vector<std::string> mBooks;
};

int main()
{
    std::unordered_map<std::string, std::any> personalInformation;

    std::string name;
    int age;
    std::vector<int> creditHistory;
    char* id;
    BookCollections books;

    name = "Michael";
    age = 20;
    creditHistory = {10,20,30};
    char michaelID[] = "US123456";
    id = michaelID;
    BookCollections michaelBooks({"Chess", "Go"});
    books = michaelBooks;

    // Storing values is easy and straightfoward
    personalInformation["name"] = name;
    personalInformation["age"] = age;
    personalInformation["credit_history"] = creditHistory;
    personalInformation["id"] = id;
    personalInformation["books"] = books;

    std::string retrievedName;
    int retrievedAge;
    std::vector<int> retrievedCreditHistory;
    char* retrievedId;
    BookCollections retrievedBooks;

    // Retrieving values requires casting the values back to its original type
    // Otherwise their types are std::any which is rather meaningless
    retrievedName = std::any_cast<std::string>(personalInformation["name"]);
    retrievedAge = std::any_cast<int>(personalInformation["age"]);
    retrievedCreditHistory = std::any_cast<std::vector<int>>(personalInformation["credit_history"]);
    retrievedId = std::any_cast<char*>(personalInformation["id"]);
    retrievedBooks = std::any_cast<BookCollections>(personalInformation["books"]);

    if ((retrievedName != name) || (retrievedAge != age) || (retrievedCreditHistory != creditHistory) || (retrievedId != id) || (retrievedBooks != books))
    {
        std::cout << "Dictionary Failed!" << std::endl;
    }
    else
    {
        std::cout << "Dictionary Worked!" << std::endl;
    }
}

We compiled and ran the C++ program and got the following result:

$ g++ cpp_dict.cpp -std=c++17 -o cpp_dict
$ ./cpp_dict
Dictionary Worked!

Differences

Even with std::any, the C++ dictionary is not as convenient as the Python dictionary. After all, it is a static typing programming language.


It should be noted that the keys in Python dictionary could be of any hashable typed values. This means that you can use string and integer intechangably as the key values for the same dictionary in Python. However, this is not the case in C++ dictionary even with std::any. In C++, we could not do std::unordered_map<std::any, std::any> because the key value of type std::any is not hashable.

References