| Home | Libraries | People | FAQ | More

## CallableTraits

### Barrett Adair

Copyright © 2016, 2017 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 24 template specializations necessary to account for all the function types in C++11:

    template<typename T> struct foo;

    //function type without varargs
    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 &&> {};

    //function type with varargs
    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 &&> {};

Things get even more complicated with member function pointers, function pointers, function references, function objects, and `transaction_safe`/`noexcept`.

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 very specific metaprogramming scenarios that can only be solved with this kind of template "spam". Writing and testing these templates is tedious and time consuming, and often requires lots of code duplication.

`Boost.CallableTraits` offers a final and decisive library-level solution to this problem, and removes the need for these specializations entirely (except for platform-specific calling conventions).

### 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` is tested on GCC 4.7.4 and later, Clang 3.5.2 and later, XCode 6.4 and later, and Visual Studio 2015. Continuous integration is managed on Appveyor for Visual Studio, and on Travis for everything else. 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.1.0 | 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 | 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` | 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` | 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` | 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` | c++17 | 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 3. XCode/AppleClang Support

feature | XCode 8 | 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