![]() |
Home | Libraries | People | FAQ | More |
Copyright © 2016 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
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...
#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:
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.
Boost.TypeTraits
and <type_traits>. CallableTraits
gives callable types similar attention, without additional metaprogramming
dependencies.
CallableTraits aims to eliminate function type template
specializations. Boost.FunctionTypes does not.
CallableTraits targets C++11/14/17 features. Boost.FunctionTypes
does not.
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.