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
Prerequisite Topics
Motivation
Regarding Boost.FunctionTypes
Compatibility
FAQ
Why should I use CallableTraits?
Why does callable_traits::args alias a std::tuple?
Why use reference collapsing rules when adding member function ref-qualifiers?
Building the test suite
add_member_const
add_member_cv
add_member_lvalue_reference
add_member_rvalue_reference
add_member_volatile
add_transaction_safe
add_varargs
apply_member_pointer
apply_return
arg_at
args
clear_args
insert_args
replace_args
remove_args
expand_args
function_type
has_calling_convention
has_varargs
has_void_return
is_const_member
is_cv_member
is_lvalue_reference_member
is_reference_member
is_rvalue_reference_member
has_member_qualifiers
is_volatile_member
remove_member_const
remove_member_cv
remove_member_pointer
remove_member_reference
remove_varargs
remove_member_volatile
push_back_args
push_front_args
pop_back_args
pop_front_args
result_of
Example: Java-style interfaces without inheritance
Contact
#include <callable_traits/callable_traits.hpp>

#include <type_traits>
#include <tuple>

using std::is_same;
using std::tuple;

using namespace callable_traits;

struct number {
    int value;
    auto add(int n) const { return value + n; }
};

using pmf = decltype(&number::add);

Manipulate member functions pointers with ease:

static_assert(is_same<
    remove_member_const_t<pmf>,
    int(number::*)(int)
>{}, "");

static_assert(is_same<
    add_member_volatile_t<pmf>,
    int(number::*)(int) const volatile
>{}, "");

static_assert(is_same<
    parent_class_of_t<pmf>,
    number
>{}, "");

static_assert(is_same<
    remove_member_pointer_t<pmf>,
    int (int) const
>{}, "");

INVOKE-aware metafunctions:

static_assert(is_same<
    args_t<pmf>,
    tuple<const number&, int>
>{}, "");

static_assert(is_same<
    arg_at_t<0, pmf>,
    const number&
>{}, "");

static_assert(is_same<
    arg_at_t<1, pmf>,
    int
>{}, "");

static_assert(is_same<
    result_of_t<pmf>,
    int
>{}, "");

static_assert(is_same<
    function_type_t<pmf>,
    int(const number&, int)
>{}, "");

static_assert(is_same<
    qualified_parent_class_of_t<pmf>,
    const number&
>{}, "");

Here are a few other trait examples:

static_assert(is_const_member<pmf>{}, "");
static_assert(!is_volatile_member<pmf>{}, "");
static_assert(!has_void_return<pmf>{}, "");
static_assert(!has_varargs<pmf>{}, "");

You can use CallableTraits to manipulate parameter lists (not defined in terms of INVOKE, since that wouldn't make sense here):

using pmf_2 = push_back_args_t<pmf, char, short, long>;

static_assert(is_same<
    pmf_2,
    int(number::*)(int, char, short, long) const
>{}, "");

static_assert(is_same<
    pop_front_args_t<pmf_2>,
    int(number::*)(char, short, long) const
>{}, "");

static_assert(is_same<
    insert_args_t<2, pmf_2, short*, long*>,
    int(number::*)(int, char, short*, long*, short, long) const
>{}, "");

static_assert(is_same<
    replace_args_t<2, pmf_2, short*, long*>,
    int(number::*)(int, char, short*, long*) const
>{}, "");

static_assert(is_same<
    remove_args_t<2, pmf_2>,
    int(number::*)(int, char, long) const
>{}, "");

static_assert(is_same<
    clear_args_t<pmf_2>,
    int(number::*)() const
>{}, "");

static_assert(is_same<
    add_varargs_t<pmf_2>,
    int(number::*)(int, char, short, long, ...) const
>{}, "");

CallableTraits is a C++11/14/17 header-only library for the inspection, synthesis, and decomposition of callable types. From const volatile && to container-like manipulation of parameter lists, CallableTraits provides all the tools you need to rid your codebase of function type specializations. CallableTraits offers a comprehensive, fine-grained assortment of traits and metafunctions for building and ripping apart C++'s most complicated and obscure types with ease. CallableTraits fills the gaps where existing library solutions fall short, aiming to be the "complete type manipulation facility for function types" mentioned in the last section of p0172, the C++17 proposal regarding "abominable function types". CallableTraits currently supports GCC 4.8 and later, Clang 3.5 and later, AppleClang from XCode 6.3 and later, and Visual Studio 2015.

CallableTraits is header-only, and does not depend on any headers outside the standard library.

CallableTraits is currently hosted at GitHub. CallableTraits is not a Boost library.

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 class template below, which defines all 48 template specializations necessary to account for all valid function types and member function pointer 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 &&> {};

//member function pointer type without varargs
template<class Return, class T, class... Args> struct foo<Return(T::*)(Args...)> {};
template<class Return, class T, class... Args> struct foo<Return(T::*)(Args...) &> {};
template<class Return, class T, class... Args> struct foo<Return(T::*)(Args...) &&> {};
template<class Return, class T, class... Args> struct foo<Return(T::*)(Args...) const> {};
template<class Return, class T, class... Args> struct foo<Return(T::*)(Args...) const &> {};
template<class Return, class T, class... Args> struct foo<Return(T::*)(Args...) const &&> {};
template<class Return, class T, class... Args> struct foo<Return(T::*)(Args...) volatile> {};
template<class Return, class T, class... Args> struct foo<Return(T::*)(Args...) volatile &> {};
template<class Return, class T, class... Args> struct foo<Return(T::*)(Args...) volatile &&> {};
template<class Return, class T, class... Args> struct foo<Return(T::*)(Args...) const volatile> {};
template<class Return, class T, class... Args> struct foo<Return(T::*)(Args...) const volatile &> {};
template<class Return, class T, class... Args> struct foo<Return(T::*)(Args...) const volatile &&> {};

//member function pointer type with varargs
template<class Return, class T, class... Args> struct foo<Return(T::*)(Args..., ...)> {};
template<class Return, class T, class... Args> struct foo<Return(T::*)(Args..., ...) &> {};
template<class Return, class T, class... Args> struct foo<Return(T::*)(Args..., ...) &&> {};
template<class Return, class T, class... Args> struct foo<Return(T::*)(Args..., ...) const> {};
template<class Return, class T, class... Args> struct foo<Return(T::*)(Args..., ...) const &> {};
template<class Return, class T, class... Args> struct foo<Return(T::*)(Args..., ...) const &&> {};
template<class Return, class T, class... Args> struct foo<Return(T::*)(Args..., ...) volatile> {};
template<class Return, class T, class... Args> struct foo<Return(T::*)(Args..., ...) volatile &> {};
template<class Return, class T, class... Args> struct foo<Return(T::*)(Args..., ...) volatile &&> {};
template<class Return, class T, class... Args> struct foo<Return(T::*)(Args..., ...) const volatile> {};
template<class Return, class T, class... Args> struct foo<Return(T::*)(Args..., ...) const volatile &> {};
template<class Return, class T, class... Args> struct foo<Return(T::*)(Args..., ...) const volatile &&> {};

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 such template "spam". While these use cases are indeed rare, the writing and testing of these templates is incredibly tedious and time consuming. On this premise, CallableTraits offers a final and decisive library-level solution, so that authors of generic code will never again need to write these specializations, for any reason.

Template specializations like those in the code snippet above still do not account for function pointers, function references, function objects/lambdas, or calling conventions. CallableTraits goes the extra mile by accounting for all of them.

[Note] Note

The upcoming ISO standard for C++17 includes a change to the core language which adds the noexcept qualifier to the type system. If the author's understanding is correct, this would increase the the count of necessary template specializations to 96 (?). Currently, C++17 noexcept qualifiers are not handled in CallableTraits, because compiler vendors have not implemented support for this feature (to best of the author's knowledge). However, features to account for and manipulate noexcept qualifiers are planned in CallableTraits, as soon as feature is implemented by a compiler vendor. Of course, the CallableTraits feature additions will be non-breaking for currently supported compiler versions.

The use cases for CallableTraits are closely related to those of Boost.FunctionTypes. Here are some reasons why you might prefer CallableTraits:

  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. 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 borrows and extends much of the functionality found in Boost.FunctionTypes, re-packaging it in a 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.


Next