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::visitorstd::visitfor variant processingEnsure all possible variant types are handled to avoid compilation errors
For more details and examples, see: