Skip to content

bfgroup/duck_invoke

Repository files navigation

Duck Invoke

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

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)

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 11; macOS Xcode 11.2.1 through 12.2; Windows MinGW-w64 7.3 and 6.3; Windows 2019 (currently only works with /std:c++latest option).

Using

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 create 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);
}