1. License
Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
2. Features
-
Header only with no external dependencies (except the
std
library). -
Single header, with included reference documentation.
-
BSL 1.0 license, so it can be used anywhere.
-
Gives equivalent functionality, with less user code, than current C++20
tag_invoke
implementations. -
For the most common, and basic, use case it’s a single line of code.
-
Fully optimizes, with
-O2
, to equivalent direct code. -
Tested to work with: Linux GCC 4.8 through 10; Linux Clang 3.8 through 10; macOS Xcode 10.1 through 11.4; Windows MinGW-w64 7.3 and 6.3; Windows VisualStudio 2017 and 2019.
3. Introduction
This small library implements a recent variation of the old idea of tag dispatching in C++:
It’s seen various uses over the ages. Some of which have been subsumed by
constexpr
constructs. This relatively new formulation attempts to solve the old
generic programming problem of providing a way to customize some library
functionality from the outside deep inside that library. The recent usage was
introduced by the WG21 P1895 paper
(tag_invoke: A general pattern for supporting customisable functions).
This particular implementation was inspired by Gašper Ažman’s presentation; 'tag_invoke' - An Actually Good Way to Do Customization Points.
Like most techniques in C++, it’s only a tool. Once that can be both beneficial
and dangerous depending on how it’s used. A goal of this utility, and the
tag_dispatch
approach is to reduce the dangers while keeping the benefits.
4. Using
4.1. Simple
The simplest use case involves a few simple items:
-
The definition of the customization point object (CPO).
-
Calling the CPO in a library that wants to be customized by users of it.
-
Defining a customization in code that uses the library.
-
Calling the library to use the feature that is customized.
(1) With this library the definition of the CPO is a simple single declaration:
#include <bfg/tag_invoke.h>
namespace compute {
BFG_TAG_INVOKE_DEF(formula);
} // namespace compute
That has the effect of declaring compute::formula_t
for the CPO tag type.
And defines a compute::formula
CPO that can be called. And that is
customizable through tag_invoke
functions.
(2) Using the CPO in the library code, or even in user code, is as simple as calling the CPO like a regular call:
template <typename Compute>
float do_compute(const Compute & c, float a, float b)
{
return compute::formula(c, a, b);
}
The key aspect for defining such code in libraries is that you need to accept
an argument for which the type is used for argument dependent lookup (ADL)
resolution. In this example the const Compute & c
argument will do that for
us.
(3) We can now turn our attention to the user code of the above "library". For that we need to crete an object that provides a customization of the appropriate customization point. The most effective way to do that is with a hidden friend function:
struct custom_compute
{
private:
friend float
tag_invoke(compute::formula_t, const custom_compute &, float a, float b)
{
return a * b;
}
};
The arguments match the use of the CPO in the library, plus the tag type of the CPO as the first argument. That argument is what puts the customization within the realm of ADL.
(4) Finally we can use the library and the customization we created for it:
int main()
{
do_compute(custom_compute{}, 2, 3);
}
4.2. Default Implementation in Namespace
It’s possible to define a default implementation such that clients of the
library don’t need to provide a customization. There is one concern when
providing a default implementation: the tag_invoke
function’s scope
needs to avoid leaking out globally. There are two simple ways of achieving
that, using a namespace or a hidden friend. Here we show how to add a default
implementation in an associated namespace.
#include <bfg/tag_invoke.h>
namespace compute {
BFG_TAG_INVOKE_DEF(formula);
template <typename Compute>
float tag_invoke(formula_t, const Compute &, float a, float b)
{
return a + b;
}
} // namespace compute
template <typename Compute>
float do_compute(const Compute & c, float a, float b)
{
return compute::formula(c, a, b);
}
We start off with a namespace for the "library" of compute
. Where we define
the CPO formula
. The default implementation comes next. Like implementing a
customization function in the client we follow the same. Two obvious
differences stand out: 1) it’s a free function in same namespace as the CPO,
2) it accepts any type for the object argument.
4.3. Default Implementation in CPO
The other option for defining a default customization implementation is to
make it a hidden friend function. To do that we can’t use the convenience
BFG_TAG_INVOKE_DEF
macro. Like the default implementation of the CPO in the
namespace, the function is the same. Only the location changes.
#include <bfg/tag_invoke.h>
namespace compute {
static struct formula_t final : ::bfg::tag<formula_t>
{
template <typename Compute>
friend float tag_invoke(formula_t, const Compute &, float a, float b)
{
return a + b;
}
} const & formula = ::bfg::tag_invoke_v(formula_t {});
} // namespace compute
template <typename Compute>
float do_compute(const Compute & c, float a, float b)
{
return compute::formula(c, a, b);
}
We now need to declare the CPO type and define the CPO ourselves. Fortunately
all the work of the body for the CPO tag type is taken care of by the utility
bfg::tag
template. All we need to do in addition is to implement the hidden
friend function tag_invoke
and define the CPO itself. For that there’s also
a utility function, bfg::tag_invoke_v
, that will return a reference to the
tag specific singleton.
4.4. Multiple Defaults
With CPOs and tag_invoke
we are not limited to overloading for the object
type argument. We can overload on any of the arguments to provide specific
implementation customizations. With that we can also provide default
implementations that use those other argument types. Having those additional
default implementations is easily done by adding overloaded tag_invoke
functions in the library.
#include <bfg/tag_invoke.h>
namespace compute {
BFG_TAG_INVOKE_DEF(formula);
template <typename Compute>
float tag_invoke(formula_t, const Compute &, float a, float b)
{
return a + b;
}
template <typename Compute>
unsigned int
tag_invoke(formula_t, const Compute &, unsigned int a, unsigned int b)
{
return a | b;
}
} // namespace compute
template <typename Compute, typename Value>
float do_compute(const Compute & c, Value a, Value b)
{
return compute::formula(c, a, b);
}
That shows how we can provide a different default algorithm if it’s float
-s
or unsigned int
-s we are accepting. The key difference to notice from other
use cases is that now the API function, do_compute
, needs to parameterize
the types of the rest of the arguments as they aren’t known ahead of time.
5. Reference
5.1. bfg::tag_invoke
The singleton function object that dispatches, through ADL, to the overload set
of the specific customization point object tag by calling the unqualified
tag_invoke
function.
5.2. bfg::tag_invoke_result_t
Obtains, for a customization point object type of Tag
called wih the arguments
Args…
, the result type of calling that CPO.
5.3. bfg::tag_invoke_is_nothrow
Detects if the call to the given customization point object, of type Tag
and
the argument types of Args…
, is marked noexcept(true)
.
5.4. bfg::tag
The bfg::tag
template defines the customization point object type. To use
inherit from it with CRTP. For example:
struct run_t final : bfg::tag<run_t> {};
This only defines the type for the CPO. To define the CPO itself you need to use
the tag_invoke_v
utility.
5.5. bfg::tag_invoke_v
The tag_invoke_v
function obtains a reference to the customization point
object singleton.
5.6. BFG_TAG_INVOKE_DEF
The BFG_TAG_INVOKE_DEF(Tag)
macro defines the customization point object with
a corresponding type. The argument is the name of the CPO to define the type
of the generated CPO will be the Tag
post-fixed with _t
. For a CPO named
run
, i.e. BFG_TAG_INVOKE_DEV(run);
it produces:
static struct run_t final : ::bfg::tag<run_t> {}
const & run = ::bfg::tag_invoke_v(run_t {});
6. Credits
Rubber duck photo for logo comes from Arnando Are.