| Home | Libraries | People | FAQ | More

## CallableTraits

### Barrett Adair

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)

## Overview

`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, "");
    }

### Motivation

> “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).

### Regarding `Boost.FunctionTypes`

The features in `Boost.CallableTraits` largely overlap with `Boost.FunctionTypes`. Here are some reasons why you might prefer `Boost.CallableTraits`:

1. `Boost.FunctionTypes` is tightly coupled to `Boost.MPL` sequences, while `Boost.CallableTraits` has no dependencies other than the standard library.
2. `Boost.CallableTraits` targets C++11 and later:
   1. `Boost.CallableTraits` treats function objects/lambdas as first-class citizens.
   2. `Boost.CallableTraits` supports lvalue/rvalue reference member qualifiers.
   3. `Boost.CallableTraits` supports `noexcept` and `transaction_safe`.
3. `Boost.FunctionTypes` does not attempt to factor all callable types into a unified, `INVOKE`-aware interface.
4. `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.

### Compatibility

`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
--- | --- | --- | --- | --- | --- | ---
`add_member_const` | c++11 | c++11 | c++11 | c++11 | c++11 (no abominables) | c++11 (no abominables)
`add_member_cv` | c++11 | c++11 | c++11 | c++11 | c++11 (no abominables) | c++11 (no abominables)
`add_member_lvalue_reference` | c++11 | c++11 | c++11 | c++11 | static_assert fails on instantiation | static_assert fails on instantiation
`add_member_rvalue_reference` | c++11 | c++11 | c++11 | c++11 | static_assert fails on instantiation | static_assert fails on instantiation
`add_member_volatile` | c++11 | c++11 | c++11 | c++11 | c++11 (no abominables) | c++11 (no abominables)
`add_noexcept` | c++17 | c++17 | static_assert fails on instantiation | static_assert fails on instantiation | static_assert fails on instantiation | static_assert fails on instantiation
`add_transaction_safe` | 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
`add_varargs` | c++11 | c++11 | c++11 | c++11 | c++11 | c++11
`apply_member_pointer` | c++11 | c++11 | c++11 | c++11 | c++11 | c++11
`apply_return` | c++11 | c++11 | c++11 | c++11 | c++11 | c++11
`args` | c++11 | c++11 | c++11 | c++11 | c++11 | c++11
`class_of` | c++11 | c++11 | c++11 | c++11 | c++11 | c++11
`function_type` | c++11 | c++11 | c++11 | c++11 | c++11 | c++11
`has_member_qualifiers` | c++11 | c++11 | c++11 | c++11 | c++11 (no abominables) | c++11 (no abominables)
`has_varargs` | c++11 | c++11 | c++11 | c++11 | c++11 | c++11
`has_void_return` | c++11 | c++11 | c++11 | c++11 | c++11 | c++11
`is_const_member` | c++11 | c++11 | c++11 | c++11 | c++11 (no abominables) | c++11 (no abominables)
`is_cv_member` | c++11 | c++11 | c++11 | c++11 | c++11 (no abominables) | c++11 (no abominables)
`is_invocable` | c++11 | c++11 | c++11 | c++11 | unknown | unknown
`is_lvalue_reference_member` | c++11 | c++11 | c++11 | c++11 | c++11 (always false) | c++11 (always false)
`is_noexcept` | c++17 | c++17 | c++11 (always false) | c++11 (always false) | c++11 (always false) | c++11 (always false)
`is_reference_member` | c++11 | c++11 | c++11 | c++11 | c++11 (always false) | c++11 (always false)
`is_rvalue_reference_member` | c++11 | c++11 | c++11 | c++11 | c++11 (always false) | c++11 (always false)
`is_transaction_safe` | 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)
`is_volatile_member` | c++11 | c++11 | c++11 | c++11 | c++11 (no abominables) | c++11 (no abominables)
`qualified_class_of` | c++11 | c++11 | c++11 | c++11 | c++11 | c++11
`remove_member_const` | c++11 | c++11 | c++11 | c++11 | c++11 (no abominables) | c++11 (no abominables)
`remove_member_cv` | c++11 | c++11 | c++11 | c++11 | c++11 (no abominables) | c++11 (no abominables)
`remove_member_reference` | c++11 | c++11 | c++11 | c++11 | c++11 (no effect) | c++11 (no effect)
`remove_member_volatile` | c++11 | c++11 | c++11 | c++11 | c++11 (no abominables) | c++11 (no abominables)
`remove_noexcept` | c++17 | c++17 | c++11 (no effect) | c++11 (no effect) | c++11 (no effect) | c++11 (no effect)
`remove_transaction_safe` | 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)
`remove_varargs` | c++11 | c++11 | c++11 | c++11 | c++11 | c++11
`return_type` | 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
--- | --- | --- | --- | --- | ---
`add_member_const` | c++11 | c++11 | c++11 | c++11 | c++11
`add_member_cv` | c++11 | c++11 | c++11 | c++11 | c++11
`add_member_lvalue_reference` | c++11 | c++11 | c++11 | c++11 | c++11
`add_member_rvalue_reference` | c++11 | c++11 | c++11 | c++11 | c++11
`add_member_volatile` | c++11 | c++11 | c++11 | c++11 | c++11
`add_noexcept` | c++17 | static_assert fails on instantiation | static_assert fails on instantiation | static_assert fails on instantiation | static_assert fails on instantiation
`add_transaction_safe` | 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
`add_varargs` | c++11 | c++11 | c++11 | c++11 | c++11
`apply_member_pointer` | c++11 | c++11 | c++11 | c++11 | c++11
`apply_return` | c++11 | c++11 | c++11 | c++11 | c++11
`args` | c++11 | c++11 | c++11 | c++11 | c++11
`class_of` | c++11 | c++11 | c++11 | c++11 | c++11
`function_type` | c++11 | c++11 | c++11 | c++11 | c++11
`has_member_qualifiers` | c++11 | c++11 | c++11 | c++11 | c++11
`has_varargs` | c++11 | c++11 | c++11 | c++11 | c++11
`has_void_return` | c++11 | c++11 | c++11 | c++11 | c++11
`is_const_member` | c++11 | c++11 | c++11 | c++11 | c++11
`is_cv_member` | c++11 | c++11 | c++11 | c++11 | c++11
`is_invocable` | c++11 | c++11 | c++11 | c++11 | c++11
`is_lvalue_reference_member` | c++11 | c++11 | c++11 | c++11 | c++11
`is_noexcept` | c++17 | c++11 (always false) | c++11 (always false) | c++11 (always false) | c++11 (always false)
`is_reference_member` | c++11 | c++11 | c++11 | c++11 | c++11
`is_rvalue_reference_member` | c++11 | c++11 | c++11 | c++11 | c++11
`is_transaction_safe` | c++11 (always false) | c++11 (always false) | c++11 (always false) | c++11 (always false) | c++11 (always false)
`is_volatile_member` | c++11 | c++11 | c++11 | c++11 | c++11
`qualified_class_of` | c++11 | c++11 | c++11 | c++11 | c++11
`remove_member_const` | c++11 | c++11 | c++11 | c++11 | c++11
`remove_member_cv` | c++11 | c++11 | c++11 | c++11 | c++11
`remove_member_reference` | c++11 | c++11 | c++11 | c++11 | c++11
`remove_member_volatile` | c++11 | c++11 | c++11 | c++11 | c++11
`remove_noexcept` | c++17 | c++11 (no effect) | c++11 (no effect) | c++11 (no effect) | c++11 (no effect)
`remove_transaction_safe` | c++11 (no effect) | c++11 (no effect) | c++11 (no effect) | c++11 (no effect) | c++11 (no effect)
`remove_varargs` | c++11 | c++11 | c++11 | c++11 | c++11
`return_type` | 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
--- | --- | --- | --- | --- | ---
`add_member_const` | c++11 | c++11 | c++11 | c++11 | c++11
`add_member_cv` | c++11 | c++11 | c++11 | c++11 | c++11
`add_member_lvalue_reference` | c++11 | c++11 | c++11 | c++11 | c++11
`add_member_rvalue_reference` | c++11 | c++11 | c++11 | c++11 | c++11
`add_member_volatile` | c++11 | c++11 | c++11 | c++11 | c++11
`add_noexcept` | unknown | static_assert fails on instantiation | static_assert fails on instantiation | static_assert fails on instantiation | static_assert fails on instantiation
`add_transaction_safe` | unknown | static_assert fails on instantiation | static_assert fails on instantiation | static_assert fails on instantiation | static_assert fails on instantiation
`add_varargs` | c++11 | c++11 | c++11 | c++11 | c++11
`apply_member_pointer` | c++11 | c++11 | c++11 | c++11 | c++11
`apply_return` | c++11 | c++11 | c++11 | c++11 | c++11
`args` | c++11 | c++11 | c++11 | c++11 | c++11
`class_of` | c++11 | c++11 | c++11 | c++11 | c++11
`function_type` | c++11 | c++11 | c++11 | c++11 | c++11
`has_member_qualifiers` | c++11 | c++11 | c++11 | c++11 | c++11
`has_varargs` | c++11 | c++11 | c++11 | c++11 | c++11
`has_void_return` | c++11 | c++11 | c++11 | c++11 | c++11
`is_const_member` | c++11 | c++11 | c++11 | c++11 | c++11
`is_cv_member` | c++11 | c++11 | c++11 | c++11 | c++11
`is_invocable` | c++11 | c++11 | c++11 | c++11 | c++11
`is_lvalue_reference_member` | c++11 | c++11 | c++11 | c++11 | c++11
`is_noexcept` | unknown | c++11 (always false) | c++11 (always false) | c++11 (always false) | c++11 (always false)
`is_reference_member` | c++11 | c++11 | c++11 | c++11 | c++11
`is_rvalue_reference_member` | c++11 | c++11 | c++11 | c++11 | c++11
`is_transaction_safe` | unknown | c++11 (always false) | c++11 (always false) | c++11 (always false) | c++11 (always false)
`is_volatile_member` | c++11 | c++11 | c++11 | c++11 | c++11
`qualified_class_of` | c++11 | c++11 | c++11 | c++11 | c++11
`remove_member_const` | c++11 | c++11 | c++11 | c++11 | c++11
`remove_member_cv` | c++11 | c++11 | c++11 | c++11 | c++11
`remove_member_reference` | c++11 | c++11 | c++11 | c++11 | c++11
`remove_member_volatile` | c++11 | c++11 | c++11 | c++11 | c++11
`remove_noexcept` | unknown | c++11 (no effect) | c++11 (no effect) | c++11 (no effect) | c++11 (no effect)
`remove_transaction_safe` | unknown | c++11 (no effect) | c++11 (no effect) | c++11 (no effect) | c++11 (no effect)
`remove_varargs` | c++11 | c++11 | c++11 | c++11 | c++11
`return_type` | 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)
--- | --- | ---
`add_member_const` | c++11 | c++11
`add_member_cv` | c++11 | c++11
`add_member_lvalue_reference` | c++11 | c++11
`add_member_rvalue_reference` | c++11 | c++11
`add_member_volatile` | c++11 | c++11
`add_noexcept` | static_assert fails on instantiation | static_assert fails on instantiation
`add_transaction_safe` | static_assert fails on instantiation | static_assert fails on instantiation
`add_varargs` | c++11 (define BOOST_DISABLE_WIN32 to disable __cdecl on PMF varargs) | c++11 (define BOOST_DISABLE_WIN32 to disable __cdecl on PMF varargs)
`apply_member_pointer` | c++11 | c++11
`apply_return` | c++11 | c++11
`args` | c++11 | c++11
`class_of` | c++11 | c++11
`function_type` | c++11 | c++11
`has_member_qualifiers` | c++11 | c++11
`has_varargs` | c++11 (define BOOST_DISABLE_WIN32 to disable __cdecl on PMF varargs) | c++11 (define BOOST_DISABLE_WIN32 to disable __cdecl on PMF varargs)
`has_void_return` | c++11 | c++11
`is_const_member` | c++11 | c++11
`is_cv_member` | c++11 | c++11
`is_invocable` | c++11 | c++11 (always false for functions that are simultaneously ref and cv-qualified due to compiler bug)
`is_lvalue_reference_member` | c++11 | c++11
`is_noexcept` | c++11 (always false) | c++11 (always false)
`is_reference_member` | c++11 | c++11
`is_rvalue_reference_member` | c++11 | c++11
`is_transaction_safe` | c++11 (always false) | c++11 (always false)
`is_volatile_member` | c++11 | c++11
`qualified_class_of` | c++11 | c++11
`remove_member_const` | c++11 | c++11
`remove_member_cv` | c++11 | c++11
`remove_member_reference` | c++11 | c++11
`remove_member_volatile` | c++11 | c++11
`remove_noexcept` | c++11 (no effect) | c++11 (no effect)
`remove_transaction_safe` | c++11 (no effect) | c++11 (no effect)
`remove_varargs` | c++11 (define BOOST_DISABLE_WIN32 to disable __cdecl on PMF varargs) | c++11 (define BOOST_DISABLE_WIN32 to disable __cdecl on PMF varargs)
`return_type` | c++11 | c++11