C++17 std::variant: A Versatile Tool for Polymorphic Values

beginner
17 min

C++17 std::variant: A Versatile Tool for Polymorphic Values

Welcome back to CodeYourCraft! Today, we're going to dive into a powerful feature of C++17: std::variant. This tool is a game-changer for managing different types of data in a single variable, making your code cleaner, more efficient, and easier to understand.

Let's start with the basics:

What is std::variant?

šŸ’” Pro Tip: Think of std::variant as a Swiss Army knife for values. It can hold different types of data, but only one type at a time.

cpp
std::variant<int, std::string, double> myVariant;

In this example, myVariant can store an int, a std::string, or a double.

Creating and Initializing std::variant

There are three ways to create and initialize a std::variant:

  1. Default constructor: Creates an empty std::variant.
cpp
std::variant<int, std::string, double> myVariant;
  1. Constructor with initial value: Creates a std::variant with the specified initial value.
cpp
std::variant<int, std::string, double> myVariant(42); // initial value: 42 (of type int)
  1. Constructor with initializer list: Creates a std::variant with the specified initial values.
cpp
std::variant<int, std::string, double> myVariant = {42, "Hello", 3.14}; // initial value: 42 (of type int)

Accessing the Stored Value

šŸ“ Note: To access the stored value, you need to use std::get<index>() where index is the position of the type you're interested in. Remember, the first type has index 0.

cpp
std::variant<int, std::string, double> myVariant(42); int value = std::get<0>(myVariant); // value now equals 42

Changing the Stored Value

To change the stored value, you can assign a new value to the std::variant like any other variable.

cpp
std::variant<int, std::string, double> myVariant(42); myVariant = "New Value"; // myVariant now stores a std::string

Handling Errors with std::visit

When accessing the stored value, it's essential to check if the std::variant is in a valid state. Use std::holds_alternative<type>() to check if the std::variant contains the desired type. If not, handle the error appropriately.

cpp
std::variant<int, std::string, double> myVariant(42); if (std::holds_alternative<int>(myVariant)) { int value = std::get<0>(myVariant); // do something with value } else if (std::holds_alternative<std::string>(myVariant)) { std::string value = std::get<1>(myVariant); // do something with value } else { std::cerr << "Error: Unsupported type in std::variant." << std::endl; }

Using std::visit for Simpler Error Handling

šŸ’” Pro Tip: Use std::visit to simplify error handling. It calls a function for the first matching type in the std::variant.

cpp
#include <functional> struct MyVisitor { void operator()(int value) { // do something with value } void operator()(const std::string& value) { // do something with value } void operator()(double value) { // do something with value } }; std::variant<int, std::string, double> myVariant(42); std::visit(MyVisitor{}, myVariant);

Example: Simple Bank Account

Let's create a simple bank account class that uses std::variant to store the account balance as either an int (for normal accounts) or a std::string (for savings accounts with a fixed interest rate).

cpp
#include <string> #include <variant> #include <iostream> struct BankAccount { std::string name; std::variant<int, std::string> balance; BankAccount(const std::string& name, int initialBalance) : name(name), balance(initialBalance) {} BankAccount(const std::string& name, const std::string& initialBalance) : name(name), balance(initialBalance) {} void deposit(int amount) { if (std::holds_alternative<int>(balance)) { int currentBalance = std::get<0>(balance); balance = currentBalance + amount; } else { std::cerr << "Error: Cannot deposit into savings account." << std::endl; } } void deposit(const std::string& amount) { if (!std::holds_alternative<std::string>(balance)) { std::cerr << "Error: Cannot deposit into normal account." << std::endl; } else { std::string currentBalance = std::get<1>(balance); // Convert the deposit amount to a double, add the interest, and convert back to a string double newBalance = std::stod(currentBalance) + 0.01 * std::stod(currentBalance); balance = newBalance > 1000 ? new std::string(std::to_string(static_cast<int>(newBalance))) : newBalance; } } void printBalance() const { std::visit([&](auto balanceValue) { std::cout << name << "'s current balance: " << balanceValue << std::endl; }, balance); } }; int main() { BankAccount john("John", 500); john.printBalance(); john.deposit(100); john.printBalance(); BankAccount savingsAccount("Savings Account", "1000"); savingsAccount.printBalance(); savingsAccount.deposit(50); savingsAccount.printBalance(); return 0; }
Quick Quiz
Question 1 of 1

What is `std::variant` in C++17?

Quick Quiz
Question 1 of 1

How do you access the stored value in a `std::variant`?