Dynamic Dispatcher
A driving philosophy behind SVS is to allow compile-time specialization on as many features as possible, while still providing generic fallbacks.
This has an interesting interaction with the type-erasure techniques used throughout the library, namely:
How to we gather the available specializations for a given type-erased together in one place?
Given a collection of argument values, how do we pick the “best” specialization?
The solution to this is the svs::lib::Dispatcher
.
Example with Exposition
Motivation
We begin with a motivating example. Suppose we have a generic method
struct Converted {
std::string value_;
};
void generic(
svs::DataType a_type,
svs::DataType b_type,
svs::lib::ExtentTag<svs::Dynamic> SVS_UNUSED(extent_tag),
Converted converted
) {
fmt::print(
"Generic: {}, {}, {} with arg \"{}\"\n",
a_type,
b_type,
format_extent(svs::Dynamic),
converted.value_
);
}
That operates on dynamic types a_type
and b_type
, a dynamically determined extent
(vector dimension), and takes some type Converted
as the final argument.
Next, suppose we can do better if we hoist a_type
and b_type
into the type domain
for some select types of interest, and perhaps propagate some static extent as well.
So, we define a specialization:
// A specialized method.
// SVS defines the dispatch conversion from `svs::DataType` to `svs::lib::Type`.
// This overload takes an additional `std::string` argument type.
template <typename A, typename B, size_t N>
void specialized(
svs::lib::Type<A> a_type,
svs::lib::Type<B> b_type,
svs::lib::ExtentTag<N> SVS_UNUSED(extent_tag),
const std::string& arg
) {
// Convert `svs::lib::Type` to `svs::DataType`.
svs::DataType a = a_type;
svs::DataType b = b_type;
fmt::print(
"Specialized with string: {}, {}, {} with arg \"{}\"\n", a, b, format_extent(N), arg
);
}
Finally, maybe we can do even better if we can specialize the final argument on a boolean instead of a string:
template <typename A, typename B, size_t N>
void specialized_alternative(
svs::lib::Type<A> a_type,
svs::lib::Type<B> b_type,
svs::lib::ExtentTag<N> SVS_UNUSED(extent_tag),
bool flag
) {
// Convert `svs::lib::Type` to `svs::DataType`.
svs::DataType a = a_type;
svs::DataType b = b_type;
fmt::print(
"Specialized with flag: {}, {}, {} with arg \"{}\"\n", a, b, format_extent(N), flag
);
}
At this point, we have three methods: a generic fallback and two specialized templates. In our final program, we cannot instantiate our specialized implementations for all possible types and values as that either would bloat the final binary significantly or simply be unfeasible.
In an ideal world, we could define a list of selected specializations in a centralized location, invoke those when our run-time parameters match those specializations, and invoke the fallback otherwise.
This is where the svs::lib::Dispatcher
comes into play.
Dispatcher
// A variant of types given for the last argument of the methods we wish to dispatch to.
using Variant = std::variant<bool, std::string>;
// A dispatcher wrapping and dispatching to functions that return void and whose arguments
// are constructible using dispatcher conversion from the remaining types.
using Dispatcher =
svs::lib::Dispatcher<void, svs::DataType, svs::DataType, svs::lib::ExtentArg, Variant>;
Above, we see the definition of a std::variant
(corresponding to the final
std::string
or bool
arguments of our specializations.
Next, there is the definition of a dispatcher taking four arguments and returning void
.
With this dispatcher, we can register our specializations and generic fallback:
Dispatcher build_and_register() {
// Default construction for a dispatcher.
auto dispatcher = Dispatcher{};
// When registering methods, argument documentation can be requested using this tag.
// Note dispatch rules are not required to implement documentation, in which case
// default documentation will be provided.
constexpr auto build_docs = svs::lib::dispatcher_build_docs;
// Register the desired specializations.
dispatcher.register_target(build_docs, &specialized<float, float, svs::Dynamic>);
dispatcher
.register_target(build_docs, &specialized_alternative<float, float, svs::Dynamic>);
dispatcher.register_target(build_docs, &specialized<uint32_t, uint8_t, 128>);
// Register the dynamic fallback.
dispatcher.register_target(build_docs, &generic);
return dispatcher;
}
const Dispatcher& get_dispatcher() {
// Only allocated and populate the dispatcher once.
static Dispatcher dispatcher = build_and_register();
return dispatcher;
}
In the above snippet, we see target registration by passing a reference to the fully-instantiated specializations and a single instance of the generic method.
Note
Passing C++ functions by reference is an acceptable way to pass the desired dispatch target. Mechanically, a method representing the full specialization will be compiled and the function reference will decay to a function pointer to this specialization.
It is also acceptable to pass a lambda directly by value.
When passing a lambda, it is crucial to ensure that any value captured by reference properly outlives the life of the dispatcher.
Upon registration, SVS will check that all source argument types of the dispatcher are
convertible to the argument types of the target by checking for a specialization of
svs::lib::DispatcherConverter
.
The converter defines match suitability (whether a conversion from source value to
destination type is possible and if so, how “good” that conversion is) and the actual
argument conversion.
SVS already contains rules for converting svs::DataType
to svs::lib::Type
, rules
for recursively matching std::variant
types, and conversions between different
applicable reference qualifiers of the same type.
To hook in the custom Converted
type into this system, we can define our own conversion
rules:
// Define full-specializations for converting `std::string` and `bool` to `Converted`.
template <> struct svs::lib::DispatchConverter<std::string, Converted> {
// Return ``true`` if this is a match. Otherwise, return false.
//
// To provide finer-grained control, an ``int64_t`` can be returned instead, where
// negative values indicate an invalid match (a method that has an invalid match for
// any argument must be discarded), and positive values indicate degree of matches
// with lower numbers having higher priority.
static bool match(const std::string& SVS_UNUSED(arg)) { return true; }
// This argument is called when a method has been selected and we're ready to convert
// the source argument to the destination argument and invoke the registered target.
static Converted convert(const std::string& arg) { return Converted{arg}; }
// Provide documentation regarding the values accepted by this conversion.
static std::string_view description() { return "all-string-values"; }
};
template <> struct svs::lib::DispatchConverter<bool, Converted> {
static bool match(bool SVS_UNUSED(arg)) { return true; }
static Converted convert(bool arg) { return Converted{fmt::format("boolean {}", arg)}; }
static std::string_view description() { return "all-boolean-values"; }
};
With these rules defined, SVS fill figure out how to convert each alternative in the
variant into a Converted
if needed.
Example Runs
Now, a main function can be defined that parses commandline arguments into the dispatch types and invokes the overload resolution logic in the dispatcher.
int svs_main(const std::vector<std::string>& args) {
// Perform some very basic
const size_t nargs = args.size();
bool requested_help = std::any_of(args.begin(), args.end(), [](const auto& arg) {
return arg == "--help" || arg == "help";
});
if (nargs != 6 || requested_help) {
print_help();
return 0;
}
// Parse the argument types.
svs::DataType type_a = parse_datatype(args.at(1));
svs::DataType type_b = parse_datatype(args.at(2));
svs::lib::ExtentArg extent_arg = parse_extent_arg(args.at(3), parse_bool(args.at(4)));
// Construct a variant according to the value of `arg`.
const auto& arg = args.at(5);
auto maybe_bool = parse_bool_nothrow(arg);
auto variant = [&]() -> Variant {
if (maybe_bool) {
return maybe_bool.value();
}
return arg;
}();
// Instantiate the dispatcher and dispatch to the best fitting method.
get_dispatcher().invoke(type_a, type_b, extent_arg, variant);
return 0;
}
Possible runs may look like this:
Input: float16 float16 128 false hello
Output: Generic: float16, float16, dynamic with arg "hello"
Input: float16 float16 128 false true
Output: Generic: float16, float16, dynamic with arg "boolean true"
Input: float16 float16 128 true true
Output: ANNException (no match found)
Input: uint32 uint8 128 true hello
Output: Specialized with string: uint32, uint8, 128 with arg "hello"
Input: float32 float32 100 false false
Output: Specialized with flag: float32, float32, dynamic with arg "false"
Automatic Documentation Generation
One advantage of grouping all methods together in a single place is that we can use the documentation feature of the dispatcher to describe all registered methods. The code for the help message for our example executable is show below
void print_help() {
constexpr std::string_view help_template = R"(
Usage:
(1) dispatcher type_a type_b dims enforce_dims arg
(2) dispatcher --help
1. Run the dispatcher example.
* type_a and type_b: must be parseable as s `svs::DataType`.
* dims: The number of dimensions to dispatch on. Can either be an integer or the string
"dynamic"
* enforce_dims: Whether or not relaxation to dynamic dimensionality is allowed. Must
either be "true" or "false"
* arg: An additional string argument. If arg is either "true" or "false", it will be
parsed as a boolean. Otherwise, it will remain as a string and be forwarded to the
appropriate overload.
2. Print this help message.
Registered Specializations
--------------------------
{{ type A, type B, Extent, Last Argument }}
{}
)";
const auto& dispatcher = get_dispatcher();
auto method_docs = std::vector<std::string>();
constexpr size_t nargs = Dispatcher::num_args();
for (size_t i = 0, imax = dispatcher.size(); i < imax; ++i) {
auto arg_docs = std::vector<std::string>();
for (size_t j = 0; j < nargs; ++j) {
arg_docs.push_back(dispatcher.description(i, j));
}
method_docs.push_back(fmt::format("{{ {} }}", fmt::join(arg_docs, ", ")));
}
fmt::print(help_template, fmt::join(method_docs, "\n"));
}
The generated help message might look something like this:
Registered Specializations
--------------------------
{ type A, type B, Extent, Last Argument }
{ float32, float32, any, all values -- (union alternative 1) }
{ float32, float32, any, all values -- (union alternative 0) }
{ uint32, uint8, 128, all values -- (union alternative 1) }
{ all values, all values, any, all-boolean-values OR all-string-values -- (union alternatives 0, 1) }
API Documentation
Classes
-
template<typename Ret, typename ...Args>
class Dispatcher A dynamic, multi-method dispatcher for registering specializations.
Multiple target methods can be registered with the dispatcher, provided that each target method has the same number of arguments and dispatch conversion between each target argument type and its corresponding member in
Args
is defined.When invoked, the dispatcher will find the most applicable registered target by applying
svs::lib::dispatch_match
on its arguments and the argument types of each registered method.The most specific applicable method will then be invoked by calling
svs::lib::dispatch_convert
on each argument to its corresponding target type.- Template Parameters:
Ret – The return type of invoking ai contained method.
Args – The run-time arguments to dispatch over.
Public Types
Public Functions
-
Dispatcher() = default
Construct an empty dispatcher.
-
inline size_t size() const
Return the number of registered candidates.
-
template<typename F>
inline void register_target(BuildDocsTag build_docs, F f) Register a callable with the dispatcher with conversion documentation.
-
inline std::pair<std::optional<size_t>, match_type> best_match(const std::remove_cvref_t<Args>&... args) const
Get the index and the score of the best match.
If no match is found, then the optional in the return value will be empty. In this case, the contents of the
match_type
is undefined.
-
inline bool has_match(const std::remove_cvref_t<Args>&... args) const
Return whether or not the given collection of arguments can be matched with any method registered in the dispatcher.
-
inline std::string description(size_t method, size_t argument) const
Return dispatch documentation for the given method and argument.
Throws an
svs::ANNException
ifmethod >= size()
orargument >= num_args()
.
Public Static Functions
-
static inline constexpr size_t num_args()
Return the number of arguments the dispatcher expects to receive.
-
template<typename Ret, typename ...Args>
class DispatchTarget Method wrapper for the target of a dispatch operation.
- Template Parameters:
Ret – The return type of the method.
Args – The dispatch argument types of the method.
Public Types
-
using signature_type = detail::Signature<Ret(Args...)>
The full signature of the dispatch arguments.
Public Functions
-
template<typename Callable>
inline DispatchTarget(NoDocsTag tag, Callable f) Construct a DispatchTarget around the callable
f
with no documentation.The following requirements must hold:
The argument types of
f
must be deducible (in other words,f
cannot have an overloaded call operator nor can its call operator be templated).The number of arguments of
f
must matchDispatcher::num_args
.Furthermore, dispatch conversion must be defined between each dispatch argument and its corresponding argument in
f
.
If any of these requirements fails, this method should not compile.
- Parameters:
tag – Indicate no argument conversion documention is required.
f – The function to wrap for dispatch. The wrapped functor must have a const-qualified call operator and no non-const-qualified call operator.
-
template<typename Callable>
inline DispatchTarget(BuildDocsTag tag, Callable f) Construct a DispatchTarget around the callable
f
with documentation.The following requirements must hold:
The argument types of
f
must be deducible (in other words,f
cannot have an overloaded call operator nor can its call operator be templated).The number of arguments of
f
must matchDispatcher::num_args
.Furthermore, dispatch conversion must be defined between each dispatch argument and its corresponding argument in
f
.
If any of these requirements fails, this method should not compile.
- Parameters:
tag – Indicate argument conversion documention is required.
f – The function to wrap for dispatch. The wrapped functor must have a const-qualified call operator and no non-const-qualified call operator.
-
inline match_type check_match(const std::remove_cvref_t<Args>&... args) const
Return the result of matching each argument with the wrapped method.
-
inline Ret invoke(Args&&... args) const
Invoke the wrapped method by dispatch-converting each argument.
-
inline std::string description(size_t i) const
Return dispatch documentation for argument
i
.If
i >= num_args
, throwssvs::ANNException
indicating a bounds error. If the DispatchTarget was constructed without documentation, then this function returns the string “unknown”>
Dispatch API
-
template<typename From, typename To>
struct DispatchConverter : public std::false_type Customization point for defining dispatch conversion rules.
Expected API:
template<> struct DispatchConverter<From, To> { // Return a score for matching arguments of type `From` to type `To`. // * Negative values indicate an invalid match (cannot convert). // * Non-negative values are scored with lower values given higher priority. static int64_t match(const std::remove_cvref_t<From>&); // Perform a dispatch conversion. // This behavior of this function is undefined if `match` returns an invalid score. static To convert(From); // An optional method describing the acceptable value for this conversion. static std::string description(); }
Note that specialization requires full cv-ref qualification of the
From
andTo
types in order to be applicable.
Warning
doxygenconcept: Cannot find concept “svs::lib::DispatchConvertible” in doxygen xml output for project “SVS” from directory: /home/runner/work/_temp/build_docs/docs/doxygen/xml
-
template<typename From, typename To>
constexpr int64_t svs::lib::dispatch_match(const std::remove_cvref_t<From> &x) Return the matching score of an instance of type
From
to the typeTo
.All non-negative results should be considered with a lower number having a higher priority. So
0
has the highest priority, followed by1
, then2
etc.Internally, this method calls
svs:lib::DispatchConverter<From, To>::match
and forward the result. However, if theDispatchConverter
returns abool
, then the return value will be converted to the canonicalint64_t
representation appropriately.- Parameters:
x – The value being dispatched on.
- Returns:
An signed integer score. Scores less than 0 imply invalid match and the entire method being considered should be discarded.
-
template<typename From, typename To>
constexpr To svs::lib::dispatch_convert(From &&x) Use dispatch conversion to convert a value of type
From
toTo
.It is undefined behavior to call this method if
svs::lib::dispatch_match<From, To>(x)
is invalid.
-
template<typename From, typename To>
auto svs::lib::dispatch_description() -> std::string Return documentation for a dispatch conversion, if available.
If Such conversion is not available (
svs::lib::DispatchConverter<From, To>::description()
was not defined), then returns the sentinel string “unknown”.
-
constexpr BuildDocsTag svs::lib::dispatcher_build_docs = {}
Tag to build argument-conversion documentation.
-
constexpr NoDocsTag svs::lib::dispatcher_no_docs = {}
Tag to suppress argument-conversion documentation.
Predefined Scores
-
constexpr int64_t svs::lib::invalid_match = -1
The worst possible invalid match.
-
constexpr int64_t svs::lib::perfect_match = 0
The best possible match.
-
constexpr int64_t svs::lib::imperfect_match = 1
The next best possible match.
-
constexpr int64_t svs::lib::implicit_match = 10000
Match was found using an implicit conversion.
This conversion is performed on arguments with the same type (or ref-compatible types). Use a non-zero value to allow specializations to provide better matches than the type identity.
Helpers
Warning
doxygenconcept: Cannot find concept “svs::lib::ImplicitlyDispatchConvertible” in doxygen xml output for project “SVS” from directory: /home/runner/work/_temp/build_docs/docs/doxygen/xml
-
template<DispatchCategory Cat, typename To, typename ...Ts>
struct VariantDispatcher Match all applicable alternatives of a variant to the destination type.
Public Static Functions
-
static inline constexpr int64_t match(const std::variant<Ts...> &x)
Match the current alternative in the variant to
To
.If an alternative type is not
svs::lib::DispatchConvertible
withTo
, then returnsvs::lib::invalid_match
.
-
static inline constexpr To convert(variant_type x)
Dispatch convert the current alternative in the variant to the type
To
.Throws
svs::ANNException
if such a conversion is undefined.
-
static inline std::string description()
Document all possible conversion from the variant to
To
.
-
static inline constexpr int64_t match(const std::variant<Ts...> &x)
Full Example
The full example described at the beginning is given below.
#include "svs/lib/dispatcher.h"
#include "svs/third-party/fmt.h"
#include "svsmain.h"
#include <string_view>
namespace {
std::optional<bool> parse_bool_nothrow(std::string_view arg) {
if (arg == "true") {
return true;
}
if (arg == "false") {
return false;
}
return std::nullopt;
}
/// Parse a string as a boolean.
///
/// Throws a ``svs::ANNException`` if parsing fails.
bool parse_bool(std::string_view arg) {
auto v = parse_bool_nothrow(arg);
if (!v) {
throw ANNEXCEPTION(
"Cannot parse \"{}\" as a boolean value! Expected either \"true\" or "
"\"false\".",
arg
);
}
return v.value();
}
/// Parse a string as a valid ``svs::DataType``.
svs::DataType parse_datatype(std::string_view arg) {
auto type = svs::parse_datatype(arg);
if (type == svs::DataType::undef) {
throw ANNEXCEPTION("Cannot parse {} as an SVS datatype!", type);
}
return type;
}
/// Parse a string as an extent.
svs::lib::ExtentArg parse_extent_arg(const std::string& extent, bool enforce) {
if (extent == "dynamic") {
return svs::lib::ExtentArg{svs::Dynamic, enforce};
}
// Try to parse as an integer.
return svs::lib::ExtentArg{std::stoull(extent), enforce};
}
std::string format_extent(size_t n) {
return n == svs::Dynamic ? std::string("dynamic") : fmt::format("{}", n);
}
//! [specialization-1]
// A specialized method.
// SVS defines the dispatch conversion from `svs::DataType` to `svs::lib::Type`.
// This overload takes an additional `std::string` argument type.
template <typename A, typename B, size_t N>
void specialized(
svs::lib::Type<A> a_type,
svs::lib::Type<B> b_type,
svs::lib::ExtentTag<N> SVS_UNUSED(extent_tag),
const std::string& arg
) {
// Convert `svs::lib::Type` to `svs::DataType`.
svs::DataType a = a_type;
svs::DataType b = b_type;
fmt::print(
"Specialized with string: {}, {}, {} with arg \"{}\"\n", a, b, format_extent(N), arg
);
}
//! [specialization-1]
//! [specialization-2]
template <typename A, typename B, size_t N>
void specialized_alternative(
svs::lib::Type<A> a_type,
svs::lib::Type<B> b_type,
svs::lib::ExtentTag<N> SVS_UNUSED(extent_tag),
bool flag
) {
// Convert `svs::lib::Type` to `svs::DataType`.
svs::DataType a = a_type;
svs::DataType b = b_type;
fmt::print(
"Specialized with flag: {}, {}, {} with arg \"{}\"\n", a, b, format_extent(N), flag
);
}
//! [specialization-2]
//! [generic-fallback]
struct Converted {
std::string value_;
};
void generic(
svs::DataType a_type,
svs::DataType b_type,
svs::lib::ExtentTag<svs::Dynamic> SVS_UNUSED(extent_tag),
Converted converted
) {
fmt::print(
"Generic: {}, {}, {} with arg \"{}\"\n",
a_type,
b_type,
format_extent(svs::Dynamic),
converted.value_
);
}
//! [generic-fallback]
} // namespace
//! [converted-dispatch-conversion-rules]
// Define full-specializations for converting `std::string` and `bool` to `Converted`.
template <> struct svs::lib::DispatchConverter<std::string, Converted> {
// Return ``true`` if this is a match. Otherwise, return false.
//
// To provide finer-grained control, an ``int64_t`` can be returned instead, where
// negative values indicate an invalid match (a method that has an invalid match for
// any argument must be discarded), and positive values indicate degree of matches
// with lower numbers having higher priority.
static bool match(const std::string& SVS_UNUSED(arg)) { return true; }
// This argument is called when a method has been selected and we're ready to convert
// the source argument to the destination argument and invoke the registered target.
static Converted convert(const std::string& arg) { return Converted{arg}; }
// Provide documentation regarding the values accepted by this conversion.
static std::string_view description() { return "all-string-values"; }
};
template <> struct svs::lib::DispatchConverter<bool, Converted> {
static bool match(bool SVS_UNUSED(arg)) { return true; }
static Converted convert(bool arg) { return Converted{fmt::format("boolean {}", arg)}; }
static std::string_view description() { return "all-boolean-values"; }
};
//! [converted-dispatch-conversion-rules]
namespace {
//! [dispatcher-definition]
// A variant of types given for the last argument of the methods we wish to dispatch to.
using Variant = std::variant<bool, std::string>;
// A dispatcher wrapping and dispatching to functions that return void and whose arguments
// are constructible using dispatcher conversion from the remaining types.
using Dispatcher =
svs::lib::Dispatcher<void, svs::DataType, svs::DataType, svs::lib::ExtentArg, Variant>;
//! [dispatcher-definition]
//! [register-methods]
Dispatcher build_and_register() {
// Default construction for a dispatcher.
auto dispatcher = Dispatcher{};
// When registering methods, argument documentation can be requested using this tag.
// Note dispatch rules are not required to implement documentation, in which case
// default documentation will be provided.
constexpr auto build_docs = svs::lib::dispatcher_build_docs;
// Register the desired specializations.
dispatcher.register_target(build_docs, &specialized<float, float, svs::Dynamic>);
dispatcher
.register_target(build_docs, &specialized_alternative<float, float, svs::Dynamic>);
dispatcher.register_target(build_docs, &specialized<uint32_t, uint8_t, 128>);
// Register the dynamic fallback.
dispatcher.register_target(build_docs, &generic);
return dispatcher;
}
const Dispatcher& get_dispatcher() {
// Only allocated and populate the dispatcher once.
static Dispatcher dispatcher = build_and_register();
return dispatcher;
}
//! [register-methods]
} // namespace
//! [print-help]
void print_help() {
constexpr std::string_view help_template = R"(
Usage:
(1) dispatcher type_a type_b dims enforce_dims arg
(2) dispatcher --help
1. Run the dispatcher example.
* type_a and type_b: must be parseable as s `svs::DataType`.
* dims: The number of dimensions to dispatch on. Can either be an integer or the string
"dynamic"
* enforce_dims: Whether or not relaxation to dynamic dimensionality is allowed. Must
either be "true" or "false"
* arg: An additional string argument. If arg is either "true" or "false", it will be
parsed as a boolean. Otherwise, it will remain as a string and be forwarded to the
appropriate overload.
2. Print this help message.
Registered Specializations
--------------------------
{{ type A, type B, Extent, Last Argument }}
{}
)";
const auto& dispatcher = get_dispatcher();
auto method_docs = std::vector<std::string>();
constexpr size_t nargs = Dispatcher::num_args();
for (size_t i = 0, imax = dispatcher.size(); i < imax; ++i) {
auto arg_docs = std::vector<std::string>();
for (size_t j = 0; j < nargs; ++j) {
arg_docs.push_back(dispatcher.description(i, j));
}
method_docs.push_back(fmt::format("{{ {} }}", fmt::join(arg_docs, ", ")));
}
fmt::print(help_template, fmt::join(method_docs, "\n"));
}
//! [print-help]
//! [main]
int svs_main(const std::vector<std::string>& args) {
// Perform some very basic
const size_t nargs = args.size();
bool requested_help = std::any_of(args.begin(), args.end(), [](const auto& arg) {
return arg == "--help" || arg == "help";
});
if (nargs != 6 || requested_help) {
print_help();
return 0;
}
// Parse the argument types.
svs::DataType type_a = parse_datatype(args.at(1));
svs::DataType type_b = parse_datatype(args.at(2));
svs::lib::ExtentArg extent_arg = parse_extent_arg(args.at(3), parse_bool(args.at(4)));
// Construct a variant according to the value of `arg`.
const auto& arg = args.at(5);
auto maybe_bool = parse_bool_nothrow(arg);
auto variant = [&]() -> Variant {
if (maybe_bool) {
return maybe_bool.value();
}
return arg;
}();
// Instantiate the dispatcher and dispatch to the best fitting method.
get_dispatcher().invoke(type_a, type_b, extent_arg, variant);
return 0;
}
//! [main]
// Main helper.
SVS_DEFINE_MAIN();