Component of the Week #26: bdlf_overloaded

Summary:
  • A utility for creating an overload set of callable things (like lambdas) that is particularly useful with bsl::visit.

The bdlf::Overloaded template class allows you to construct callable objects that contain several function call overloads. bdlf::Overloaded is especially useful as a functor to apply on a variant object using bsl::visit (or std::visit), and depending on the type stored in the variant, the correct overload will be called.

This component requires C++17 and leverages class template argument deduction (CTAD) for convenient usage.

Basic Usage with Variants

The most common use case for bdlf::Overloaded is with variants, where you want to perform different operations based on the type currently held by the variant:

#include <bdlf_overloaded.h>
#include <bsl_variant.h>
#include <bsl_string.h>
#include <bsl_iostream.h>

using namespace BloombergLP;

int main() {
    bsl::variant<unsigned, double, bsl::string> v;

    bdlf::Overloaded visitor{
        [](unsigned u)           { return "Got unsigned: " + bsl::to_string(u); },
        [](double d)             { return "Got double: " + bsl::to_string(d); },
        [](const bsl::string& s) { return "Got string: " + s; }
    };

    v = 42U;
    bsl::cout << bsl::visit(visitor, v) << bsl::endl;
    // Output: Got unsigned: 42

    v = 3.14;
    bsl::cout << bsl::visit(visitor, v) << bsl::endl;
    // Output: Got double: 3.14

    v = bsl::string("hello");
    bsl::cout << bsl::visit(visitor, v) << bsl::endl;
    // Output: Got string: hello

    return 0;
}

If the bdlf::Overloaded object only needs to be used once, you can create it inline without a named variable:

bsl::string message = bsl::visit(
    bdlf::Overloaded{
        [](unsigned u) { return "Got unsigned: " + bsl::to_string(u); },
        [](double d)   { return "Got double: " + bsl::to_string(d); },
        [](const bsl::string& s) { return "Got string: " + s; }
    }, v);

Function Pointers and Member Functions

You can also use bdlf::Overloaded to create overload sets from function pointers and member functions. This allows you to encapsulate different behaviors in a single callable object, which can be particularly useful when you want to pass around a set of operations that can be applied to different types:

#include <bdlf_overloaded.h>
#include <bsl_iostream.h>
#include <bsl_string.h>
#include <bsl_variant.h>

using namespace BloombergLP;

struct Cat {
    bsl::string speak() const {
        return "Meow";
    }
};

struct Dog {
    bsl::string speak() const {
        return "Woof";
    }
};

// Note that trees can't speak on their own.
struct Tree {};

bsl::string speakForTheTrees(const Tree& tree) {
    return "The Lorax speaks for the trees!";
}

int main() {
    Cat  cat;
    Dog  dog;
    Tree tree;

    bdlf::Overloaded speak{
        &Cat::speak,     // Member function pointer for handling cats
        &Dog::speak,     // Member function pointer for handling dogs
        speakForTheTrees // Free function for handling trees
    };

    // Note that when the overload to be called is a member function,
    // `bdlf::Overloaded` expects a pointer to the object:
    bsl::cout << speak(&cat) << bsl::endl; // Output: Meow
    bsl::cout << speak(&dog) << bsl::endl; // Output: Woof
    // But function pointers are invoked according to their signature:
    bsl::cout << speak(tree) << bsl::endl; // Output: The Lorax speaks for the trees!

    return 0;
}

Benefits and Best Practices

Benefits:

  • Type Safety: Compile-time dispatch ensures type-safe operations

  • Performance: Zero-overhead abstraction

  • Composability: Easy to combine different callable types

  • Readability: Makes variant visitation code more expressive

Best Practices:

  • Use with bsl::visit or std::visit for variant processing

  • Ensure all possible variant types are handled to avoid compilation errors

For more details and examples, see: