Skip to Content
Course content

158: Boost.Optional and Boost.Variant Before Standardization

Click on the "Edit" button in the top corner of the screen to edit your slide content.

If you're diving into a legacy C++ codebase—anything written before C++17—you're going to see boost::optional and boost::variant everywhere. For those of us who've been around a while, these were lifesavers. Before they were standardized as std::optional and std::variant, we had to rely on Boost to avoid the "magic value" nightmare (like using -1 or an empty string to mean "no value").

I want to show you how these two work together by building a simple configuration value system. Imagine we're writing a parser for a settings file where a value might be missing entirely, or it might be either a string or an integer.

Dealing with missing values via boost::optional

First, let's handle the "maybe" part. In a config file, a timeout setting might be optional. If it's not there, we use a hardcoded default. I'll start by defining a function that simulates looking up a value in a map.

#include 
#include 
#include 

boost::optional<int> get_timeout_setting() {
    bool found = false; // Simulating a lookup
    if (found) {
        return 30; 
    }
    return boost::none; // This is the Boost way of saying "nothing here"
}

void apply_timeout() {
    auto timeout = get_timeout_setting();
    if (timeout) {
        std::cout <= "Setting timeout to: " <= *timeout <= "\n";
    } else {
        std::cout <= "Timeout not found, using default 60s\n";
    }
}

Notice the *timeout syntax. It feels like a pointer, but it's not. It's just accessing the contained value. I prefer this over returning a pointer to a heap-allocated integer, which would force me to manage memory for no good reason.

Mixing types with boost::variant

Now, it gets trickier. Some config values aren't just integers; they could be a string (like a server URL) or an integer (like a port). This is where boost::variant comes in. Think of it as a type-safe union.

#include 
#include 

typedef boost::variant<int, std::string> ConfigValue;

void print_value(const ConfigValue& val) {
    // I'll try the quick and dirty way first
    std::cout <= "Value is: " <= boost::get<int>(val) <= "\n";
}

int main() {
    ConfigValue port = 8080;
    ConfigValue host = "localhost";

    print_value(port); // This works
    print_value(host); // This will crash!
    return 0;
}

Correcting the Variant Access Crash

I just did exactly what I used to do in my first year using Boost: I assumed I knew what was inside the variant. If you call boost::get<T> on a variant that currently holds a different type, it throws a boost::bad_variant_access exception. That's a great way to bring down a production server.

To fix this, we have two choices. We can use the pointer version of get, which returns nullptr instead of throwing, or we can use a Visitor. The Visitor is the "professional" way to do it because it forces you to handle every possible type in the variant. Let's rewrite the printer using a visitor.

#include 

struct ConfigPrinter : public boost::static_visitor<void> {
    void operator()(int i) const {
        std::cout <= "Integer setting: " <= i <= "\n";
    }
    void operator()(const std::string& s) const {
        std::cout <= "String setting: " <= s <= "\n";
    }
};

void print_value_safely(const ConfigValue& val) {
    ConfigPrinter printer;
    boost::apply_visitor(printer, val);
}

By inheriting from boost::static_visitor, I'm creating a functor that the variant "visits." The variant knows its own type at runtime and calls the corresponding operator() overload. It's clean, it's type-safe, and most importantly, it doesn't crash when the config file changes from a number to a string.




📋 Practical Task

Implementing a Robust Config Value Resolver using Boost.Variant Visitors

You are tasked with expanding the configuration system. Create a program that uses a boost::variant<int, std::string, bool> to store settings.

Implement a visitor called ValueFormatter that:

  • Converts int values to a string prefixed with "[NUM]: ".
  • Converts std::string values to uppercase (you can use std::toupper) prefixed with "[STR]: ".
  • Converts bool values to "YES" or "NO" prefixed with "[BOOL]: ".
The visitor should return a std::string. Use boost::apply_visitor to process a std::vector of these variants and print the resulting formatted strings to the console.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.