A simple to use, single header, tag_invoke utility for C++11.

Obtain

GitHub All Releases

License

Boost Software License 1.0

Standards

Pitchfork Layout C\\ 11 C\\ 14 C\\ 17 C\\ 20

Stats

GitHub code size in bytes GitHub issues GitHub stars

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:

  1. The definition of the customization point object (CPO).

  2. Calling the CPO in a library that wants to be customized by users of it.

  3. Defining a customization in code that uses the library.

  4. 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.