Boost C++ Libraries Home Libraries People FAQ More

Next

CallableTraits

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

Overview
Motivation
Regarding Boost.FunctionTypes
Compatibility
FAQ
Reference Documentation
Member Qualifier Features
add_member_const
remove_member_const
add_member_volatile
remove_member_volatile
add_member_cv
remove_member_cv
add_member_lvalue_reference
add_member_rvalue_reference
remove_member_reference
has_member_qualifiers
is_const_member
is_volatile_member
is_cv_member
is_lvalue_reference_member
is_rvalue_reference_member
is_reference_member
Parameter List Features
function_type
args
arg_at
clear_args
remove_args
replace_args
insert_args
pop_back_args
pop_front_args
push_back_args
push_front_args
expand_args
expand_args_left
expand_args_right
add_varargs
remove_varargs
has_varargs
Return Type Features
return_type
apply_return
has_void_return
Member Pointer Features
apply_member_pointer
parent_class_of
qualified_parent_class_of
C++17 Features
add_transaction_safe
remove_transaction_safe
is_transaction_safe
add_noexcept
remove_noexcept
is_noexcept
Building the test suite
Contact

CallableTraits is a C++11/14/17 library for the inspection, synthesis, and decomposition of callable types. 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.

CallableTraits is...

  • header-only
  • dependency-free, only using the C++11 stdlib
  • cross-platform, compatible with all major C++ compilers
  • thoroughly tested
  • currently hosted at GitHub
  • not a Boost library
#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>
    >();

    // arg_at_t gives us indexed access to a parameter list
    assert_same<
        ct::arg_at_t<1, foo>,
        char
    >();

    // has_void_return lets us perform a quick check for a void return type
    static_assert(ct::has_void_return_v<foo>, "");

    // Detect C-style variadics (ellipses) in a signature (e.g. printf)
    static_assert(!ct::has_varargs_v<foo>, "");

    // 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_v<pmf>, "");

    // pop_front_args_t removes the first parameter from signature:
    assert_same<
        ct::pop_front_args_t<pmf>,
        void (foo::*)(/*int is gone!*/ char, float) const
    >();

    // clear_args_t removes all parameter types:
    assert_same<
        ct::clear_args_t<pmf>,
        void (foo::*)(/* nothing to see here! */) const
    >();
}

// This program is just a glimpse at CallableTraits' features. There
// are many more traits and metafunctions which are not shown here. Every
// feature is demonstrated and specified in the reference documentation.

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 and C++14:

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 C++17 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.

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

The use cases for CallableTraits overlaop significantly with those of Boost.FunctionTypes. Here are some reasons why you might prefer CallableTraits over the latter:

  1. Boost.FunctionTypes is tightly coupled to Boost.MPL sequences, while CallableTraits generally takes a lower-level approach. No knowledge of MPL terminology is needed to use CallableTraits.
  2. Other types in C++ receive fine-grained, low-level attention in Boost.TypeTraits and <type_traits>. CallableTraits gives callable types similar attention, without additional metaprogramming dependencies.
  3. CallableTraits aims to eliminate function type template specializations. Boost.FunctionTypes does not.
  4. CallableTraits targets C++11/14/17 features. Boost.FunctionTypes does not.
  5. The Boost.FunctionTypes interface relies heavily on tag types. CallableTraits does not.

Boost.FunctionTypes is a good tool for projects already dependent on the MPL, which must also support very old compilers. However, the Boost.FunctionTypes interface is unpleasant to use. It relies heavily on both the MPL and tag types, for problems that are more simply solved with neither. Using Boost.FunctionTypes requires an understanding of the library's "big picture."

CallableTraits reimplements much of the functionality found in Boost.FunctionTypes, but offers a much more accessible type_traits-style interface. There is nothing inherently wrong with Boost.FunctionTypes, but an MPL sequence-based solution with no C++11/14/17 support should not be the only library option for inspecting and manipulating callable types.

CallableTraits is tested on GCC 4.7.4 and later, Clang 3.5 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 reference documentation for each feature includes compatibility notes that may mention behavior differences between compiler versions. The compatibility claims in this documentation assume compilation with the highest available C++ version flag, such as -std=C++0x for older versions of GCC, or std=C++1z for GCC 6.


Next