...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
Copyright © 2016-2021 Barrett Adair
Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE.md or copy at http://www.boost.org/LICENSE_1_0.txt)
Table of Contents
Boost.CallableTraits
is a C++11 header-only library for
the inspection, synthesis, and decomposition of callable types. Boost.CallableTraits
aims to be the "complete type manipulation facility for function types"
mentioned in the final paragraph of C++17 proposal p0172,
and removes the need for template specializations for different function signatures.
C++17 noexcept
and the Transactional
Memory TS are also supported if available.
#include <type_traits> #include <tuple> #include <boost/callable_traits.hpp> namespace ct = boost::callable_traits; // This function template helps keep our example code neat template<typename A, typename B> void assert_same(){ static_assert(std::is_same<A, B>::value, ""); } // foo is a function object struct foo { void operator()(int, char, float) const {} }; int main() { // Use args_t to retrieve a parameter list as a std::tuple: assert_same< ct::args_t<foo>, std::tuple<int, char, float> >(); // has_void_return lets us perform a quick check for a void return type static_assert(ct::has_void_return<foo>::value, ""); // Detect C-style variadics (ellipses) in a signature (e.g. printf) static_assert(!ct::has_varargs<foo>::value, ""); // pmf is a pointer-to-member function: void (foo::*)(int, char, float) const using pmf = decltype(&foo::operator()); // remove_member_const_t lets you remove the const member qualifier assert_same< ct::remove_member_const_t<pmf>, void (foo::*)(int, char, float) /*no const!*/ >(); // Conversely, add_member_const_t adds a const member qualifier assert_same< pmf, ct::add_member_const_t<void (foo::*)(int, char, float)> >(); // is_const_member_v checks for the presence of member const static_assert(ct::is_const_member<pmf>::value, ""); }
“Don't try to write helper code to detect PMFs/PMDs and dispatch on them -- it is an absolute nightmare. PMF types are the worst types by far in the core language.”
-- Stephan T. Lavavej, CppCon 2015, "functional: What's New, And Proper Usage"
Consider for a moment the code below, which defines all 48 template specializations necessary to specialize for every valid function type in pure C++17:
template<typename T> struct foo; template<class Return, class... Args> struct foo<Return(Args...)> {}; template<class Return, class... Args> struct foo<Return(Args...) &> {}; template<class Return, class... Args> struct foo<Return(Args...) &&> {}; template<class Return, class... Args> struct foo<Return(Args...) const> {}; template<class Return, class... Args> struct foo<Return(Args...) const &> {}; template<class Return, class... Args> struct foo<Return(Args...) const &&> {}; template<class Return, class... Args> struct foo<Return(Args...) volatile> {}; template<class Return, class... Args> struct foo<Return(Args...) volatile &> {}; template<class Return, class... Args> struct foo<Return(Args...) volatile &&> {}; template<class Return, class... Args> struct foo<Return(Args...) const volatile> {}; template<class Return, class... Args> struct foo<Return(Args...) const volatile &> {}; template<class Return, class... Args> struct foo<Return(Args...) const volatile &&> {}; template<class Return, class... Args> struct foo<Return(Args..., ...)> {}; template<class Return, class... Args> struct foo<Return(Args..., ...) &> {}; template<class Return, class... Args> struct foo<Return(Args..., ...) &&> {}; template<class Return, class... Args> struct foo<Return(Args..., ...) const> {}; template<class Return, class... Args> struct foo<Return(Args..., ...) const &> {}; template<class Return, class... Args> struct foo<Return(Args..., ...) const &&> {}; template<class Return, class... Args> struct foo<Return(Args..., ...) volatile> {}; template<class Return, class... Args> struct foo<Return(Args..., ...) volatile &> {}; template<class Return, class... Args> struct foo<Return(Args..., ...) volatile &&> {}; template<class Return, class... Args> struct foo<Return(Args..., ...) const volatile> {}; template<class Return, class... Args> struct foo<Return(Args..., ...) const volatile &> {}; template<class Return, class... Args> struct foo<Return(Args..., ...) const volatile &&> {}; template<class Return, class... Args> struct foo<Return(Args...) noexcept> {}; template<class Return, class... Args> struct foo<Return(Args...) & noexcept> {}; template<class Return, class... Args> struct foo<Return(Args...) && noexcept> {}; template<class Return, class... Args> struct foo<Return(Args...) const noexcept> {}; template<class Return, class... Args> struct foo<Return(Args...) const & noexcept> {}; template<class Return, class... Args> struct foo<Return(Args...) const && noexcept> {}; template<class Return, class... Args> struct foo<Return(Args...) volatile noexcept> {}; template<class Return, class... Args> struct foo<Return(Args...) volatile & noexcept> {}; template<class Return, class... Args> struct foo<Return(Args...) volatile && noexcept> {}; template<class Return, class... Args> struct foo<Return(Args...) const volatile noexcept> {}; template<class Return, class... Args> struct foo<Return(Args...) const volatile & noexcept> {}; template<class Return, class... Args> struct foo<Return(Args...) const volatile && noexcept> {}; template<class Return, class... Args> struct foo<Return(Args..., ...) noexcept> {}; template<class Return, class... Args> struct foo<Return(Args..., ...) & noexcept> {}; template<class Return, class... Args> struct foo<Return(Args..., ...) && noexcept> {}; template<class Return, class... Args> struct foo<Return(Args..., ...) const noexcept> {}; template<class Return, class... Args> struct foo<Return(Args..., ...) const & noexcept> {}; template<class Return, class... Args> struct foo<Return(Args..., ...) const && noexcept> {}; template<class Return, class... Args> struct foo<Return(Args..., ...) volatile noexcept> {}; template<class Return, class... Args> struct foo<Return(Args..., ...) volatile & noexcept> {}; template<class Return, class... Args> struct foo<Return(Args..., ...) volatile && noexcept> {}; template<class Return, class... Args> struct foo<Return(Args..., ...) const volatile noexcept> {}; template<class Return, class... Args> struct foo<Return(Args..., ...) const volatile & noexcept> {}; template<class Return, class... Args> struct foo<Return(Args..., ...) const volatile && noexcept> {};
Things get even more complicated with member function pointers, function
pointers, function references, function objects, and transaction_safe
.
Granted, use cases for such obscure specializations are vitually nonexistent in run-of-the-mill application codebases. Even in library code, these are exceedingly rare. However, there are a handful of metaprogramming scenarios that can only be solved with this kind of template "spam". Writing, testing, and maintaining such code is tedious and costly.
Boost.CallableTraits
offers a final and decisive library-level
solution to this problem, and removes the need for these specializations
entirely (platform-specific calling conventions notwithstanding).
The features in Boost.CallableTraits
largely overlap with
Boost.FunctionTypes
.
Here are some reasons why you might prefer Boost.CallableTraits
:
Boost.FunctionTypes
is tightly coupled to Boost.MPL
sequences, while Boost.CallableTraits
has no dependencies
other than the standard library.
Boost.CallableTraits
targets C++11 and later:
Boost.CallableTraits
treats function objects/lambdas
as first-class citizens.
Boost.CallableTraits
supports lvalue/rvalue
reference member qualifiers.
Boost.CallableTraits
supports noexcept
and transaction_safe
.
Boost.FunctionTypes
does not attempt to factor all
callable types into a unified, INVOKE
-aware
interface.
Boost.FunctionTypes
relies heavily on "tag"
types, while Boost.CallableTraits
follows the style
of <type_traits> instead. Supporting C++11 and later in Boost.FunctionTypes
would have required significant proliferation of these tags.
For example, here is how to remove member const
from a member function pointer type in the Boost.FunctionTypes
library:
#include <type_traits> #include <boost/function_types/components.hpp> #include <boost/function_types/member_function_pointer.hpp> struct foo { void bar() const {} }; using const_removed = typename boost::function_types::member_function_pointer< typename boost::function_types::components<decltype(&foo::bar)>::types, boost::function_types::non_const>::type; static_assert(std::is_same<const_removed, void(foo::*)()>::value, ""); int main(){}
Boost.CallableTraits
makes this easier:
#include <type_traits> #include <boost/callable_traits/remove_member_const.hpp> struct foo { void bar() const {} }; using const_removed = boost::callable_traits::remove_member_const_t<decltype(&foo::bar)>; static_assert(std::is_same<const_removed, void(foo::*)()>::value, ""); int main(){}
The Boost.FunctionTypes
library includes an excellent
example
for generating type-erased interfaces (implementation here).
This example was re-implemented
using Boost.CallableTraits
to yield a slightly
more intuitive interface.
Boost.FunctionTypes
is a fine library, but its interface
left room for improvement.
Boost.CallableTraits
supports on GCC 4.7.4+, Clang 3.5.2+,
XCode 6.4+, and Visual Studio 2015+. The Intel C++ Compiler is not officially
supported, although the 2017 version for Linux does pass a handful of test
cases.
Table 1. GCC Support
feature |
GCC 7.3.0 and later |
GCC 6.3.0 |
GCC 5.4.0 |
GCC 4.9.2 |
GCC 4.8.2 |
GCC 4.7.4 |
---|---|---|---|---|---|---|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 (no abominables) |
c++11 (no abominables) |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 (no abominables) |
c++11 (no abominables) |
|
c++11 |
c++11 |
c++11 |
c++11 |
static_assert fails on instantiation |
static_assert fails on instantiation |
|
c++11 |
c++11 |
c++11 |
c++11 |
static_assert fails on instantiation |
static_assert fails on instantiation |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 (no abominables) |
c++11 (no abominables) |
|
c++17 |
c++17 |
static_assert fails on instantiation |
static_assert fails on instantiation |
static_assert fails on instantiation |
static_assert fails on instantiation |
|
c++17 (requires -fgnu-tm) |
c++17 (requires -fgnu-tm) |
static_assert fails on instantiation |
static_assert fails on instantiation |
static_assert fails on instantiation |
static_assert fails on instantiation |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 (no abominables) |
c++11 (no abominables) |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 (no abominables) |
c++11 (no abominables) |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 (no abominables) |
c++11 (no abominables) |
|
c++11 |
c++11 |
c++11 |
c++11 |
unknown |
unknown |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 (always false) |
c++11 (always false) |
|
c++17 |
c++17 |
c++11 (always false) |
c++11 (always false) |
c++11 (always false) |
c++11 (always false) |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 (always false) |
c++11 (always false) |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 (always false) |
c++11 (always false) |
|
c++17 (requires -fgnu-tm) |
c++17 (requires -fgnu-tm) |
c++11 (always false) |
c++11 (always false) |
c++11 (always false) |
c++11 (always false) |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 (no abominables) |
c++11 (no abominables) |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 (no abominables) |
c++11 (no abominables) |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 (no abominables) |
c++11 (no abominables) |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 (no effect) |
c++11 (no effect) |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 (no abominables) |
c++11 (no abominables) |
|
c++17 |
c++17 |
c++11 (no effect) |
c++11 (no effect) |
c++11 (no effect) |
c++11 (no effect) |
|
c++17 (requires -fgnu-tm) |
c++17 (requires -fgnu-tm) |
c++11 (no effect) |
c++11 (no effect) |
c++11 (no effect) |
c++11 (no effect) |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
Table 2. LLVM/Clang Support
feature |
Clang 4.0.0 and later |
Clang 3.8.0 |
Clang 3.7.1 |
Clang 3.6.2 |
Clang 3.5.2 |
---|---|---|---|---|---|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++17 |
static_assert fails on instantiation |
static_assert fails on instantiation |
static_assert fails on instantiation |
static_assert fails on instantiation |
|
static_assert fails on instantiation |
static_assert fails on instantiation |
static_assert fails on instantiation |
static_assert fails on instantiation |
static_assert fails on instantiation |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++17 |
c++11 (always false) |
c++11 (always false) |
c++11 (always false) |
c++11 (always false) |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 (always false) |
c++11 (always false) |
c++11 (always false) |
c++11 (always false) |
c++11 (always false) |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++17 |
c++11 (no effect) |
c++11 (no effect) |
c++11 (no effect) |
c++11 (no effect) |
|
c++11 (no effect) |
c++11 (no effect) |
c++11 (no effect) |
c++11 (no effect) |
c++11 (no effect) |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
Table 3. XCode/AppleClang Support
feature |
XCode 8 and later |
XCode 7.3 |
XCode 7.2 |
XCode 7.1 |
XCode 6.4 |
---|---|---|---|---|---|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
unknown |
static_assert fails on instantiation |
static_assert fails on instantiation |
static_assert fails on instantiation |
static_assert fails on instantiation |
|
unknown |
static_assert fails on instantiation |
static_assert fails on instantiation |
static_assert fails on instantiation |
static_assert fails on instantiation |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
unknown |
c++11 (always false) |
c++11 (always false) |
c++11 (always false) |
c++11 (always false) |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
unknown |
c++11 (always false) |
c++11 (always false) |
c++11 (always false) |
c++11 (always false) |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
unknown |
c++11 (no effect) |
c++11 (no effect) |
c++11 (no effect) |
c++11 (no effect) |
|
unknown |
c++11 (no effect) |
c++11 (no effect) |
c++11 (no effect) |
c++11 (no effect) |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
|
c++11 |
c++11 |
c++11 |
c++11 |
c++11 |
Table 4. Visual Studio Support
feature |
MSVC with Visual Studio 2017 |
MSVC with Visual Studio 2015 (latest update) |
---|---|---|
c++11 |
c++11 |
|
c++11 |
c++11 |
|
c++11 |
c++11 |
|
c++11 |
c++11 |
|
c++11 |
c++11 |
|
static_assert fails on instantiation |
static_assert fails on instantiation |
|
static_assert fails on instantiation |
static_assert fails on instantiation |
|
c++11 (define BOOST_DISABLE_WIN32 to disable __cdecl on PMF varargs) |
c++11 (define BOOST_DISABLE_WIN32 to disable __cdecl on PMF varargs) |
|
c++11 |
c++11 |
|
c++11 |
c++11 |
|
c++11 |
c++11 |
|
c++11 |
c++11 |
|
c++11 |
c++11 |
|
c++11 |
c++11 |
|
c++11 (define BOOST_DISABLE_WIN32 to disable __cdecl on PMF varargs) |
c++11 (define BOOST_DISABLE_WIN32 to disable __cdecl on PMF varargs) |
|
c++11 |
c++11 |
|
c++11 |
c++11 |
|
c++11 |
c++11 |
|
c++11 |
c++11 (always false for functions that are simultaneously ref and cv-qualified due to compiler bug) |
|
c++11 |
c++11 |
|
c++11 (always false) |
c++11 (always false) |
|
c++11 |
c++11 |
|
c++11 |
c++11 |
|
c++11 (always false) |
c++11 (always false) |
|
c++11 |
c++11 |
|
c++11 |
c++11 |
|
c++11 |
c++11 |
|
c++11 |
c++11 |
|
c++11 |
c++11 |
|
c++11 |
c++11 |
|
c++11 (no effect) |
c++11 (no effect) |
|
c++11 (no effect) |
c++11 (no effect) |
|
c++11 (define BOOST_DISABLE_WIN32 to disable __cdecl on PMF varargs) |
c++11 (define BOOST_DISABLE_WIN32 to disable __cdecl on PMF varargs) |
|
c++11 |
c++11 |