![]() |
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
std::function
sugarCallableTraits?CallableTraits
unique?callable_traits::args alias a std::tuple?CallableTraits alias templates not suffixed with
_t, as in std::add_lvalue_reference_t?std::is_same
or even std::is_same_v?
CallableTraits is a cross-platform C++14 library for the
inspection, decomposition, and synthesis of C++ callable types. CallableTraits
is header-only, and does not depend on any non-standard headers. CallableTraits
is currently hosted at GitHub.
![]() |
Note |
|---|---|
|
This documentation will be most beneficial to readers who posess a basic understanding of the following C++ features:
INVOKE
rules
operator()
“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 &&> {};
![]() |
Note |
|---|---|
The upcoming ISO standard for C++17 includes a change
to the core language which adds the |
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.
![]() |
Warning |
|---|---|
|
Features for the manipulation and inspection of calling conventions are
optional, which must be enabled with macro definitions. The features regarding
them are currently classified as experimental, and
are subject to breaking changes in future versions of The rationale for classifying the calling convention features as "experimental" is three-fold:
It is the author's hope that future versions of |
The use cases for CallableTraits are closely related to
those of function_traits
and Boost.FunctionTypes.
This short program showcases some, but not all, of the features available
in CallableTraits.
#include <type_traits> #include <functional> #include <tuple> #include <callable_traits/callable_traits.hpp> namespace ct = callable_traits; // foo is an example of a function object struct foo { void operator()(int, int&&, const int&, void* = nullptr) const {} }; int main() { // indexed argument types using second_arg = ct::arg_at<1, foo>; static_assert(std::is_same<second_arg, int&&>::value, ""); // arg types are packaged into std::tuple, which serves as the default // type list inCallableTraits(runtime capabilities are not used). using args = ct::args<foo>; using expected_args = std::tuple<int, int&&, const int&, void*>; static_assert(std::is_same<args, expected_args>::value, ""); //callable_traits::function_type "decays" a callable type to a plain // function type, which is structured in terms of INVOKE. using function_type = ct::function_type<foo>; using expected_function_type = void(int, int&&, const int&, void*); static_assert(std::is_same<function_type, expected_function_type>::value, ""); // By design, theCallableTraitsinterface uses constexpr // std::integral_constant functions (whenever sensible). // By also defining the appropriate overloads, this gives // users the option of using either type arguments or a value // arguments, which often eliminates the need for decltype: static_assert(ct::arity<foo>() == 4, ""); static_assert(ct::arity(foo{}) == 4, ""); // Attentive readers might notice that the type of the foo{} // expression above is foo&&, rather than foo. Indeed, //CallableTraitsis designed to also allow both ref-qualified // and cv-qualified arguments across the board: static_assert(ct::arity<foo&&>() == 4, ""); // Now, if foo had an operator() overload with a && qualifier, taking // a different number of arguments, the above static assert would fail. // For consistency, we'll avoid the value-style overloads // for the remainder of this example (whenever possible). static_assert(ct::max_arity<foo>() == 4, ""); static_assert(ct::min_arity<foo>() == 3, ""); // a quick way to check for a void return type static_assert(ct::has_void_return<foo>(), ""); // C-style variadics detection (e.g. an ellipses in a signature) static_assert(!ct::has_varargs<foo>(), ""); int i = 0; //callable_traits::can_invoke allows us to preview whether // std::invoke would compile with the given arguments. static_assert(ct::can_invoke(foo{}, 0, 0, i), ""); // no error: std::invoke(foo{}, 0, 0, i); // This call returns std::false_type, because it's an illegal call. static_assert(!ct::can_invoke(foo{}, nullptr), ""); // error: std::invoke(foo{}, nullptr); // Note that since can_invoke models std::invoke, // only a value-style function is defined. // For function objects, the following checks are determined by the // function qualifiers on operator(), rather than the qualifiers on // of the type passed. This is done for consistency with member function // pointers, where the checks below would look at the function qualifiers // (rather than qualifiers on the pointer itself). static_assert(ct::is_const_member<foo>(), ""); static_assert(!ct::is_volatile_member<foo>(), ""); static_assert(!ct::is_reference_member<foo>(), ""); static_assert(!ct::is_lvalue_reference_member<foo>(), ""); static_assert(!ct::is_rvalue_reference_member<foo>(), ""); // is_constexpr would return std::true_type if foo's operator() were constexpr. static_assert(!ct::is_constexpr<foo>(), ""); // The same check can be performed using std::integral_constant // in conjunction with function addresses: using pmf = decltype(&foo::operator()); using pmf_constant = std::integral_constant<pmf, &foo::operator()>; static_assert(!ct::is_constexpr<pmf_constant>(), ""); // So that you don't have to scroll to the top to check, // here's the type of pmf for reference. using with_const = void (foo::*)(int, int&&, const int&, void*) const; static_assert(std::is_same<pmf, with_const>::value, ""); // If you find yourself in the unfortunate-and-probably-avoidable // situation of needing to transform member function pointer // types,CallableTraitshas all the tools you need to prolong // your sanity. //CallableTraitslets you manipulate qualifiers on PMF types. // To remove const: using mutable_pmf = ct::remove_member_const<pmf>; using without_const = void (foo::*)(int, int&&, const int&, void*) /*no const!*/; static_assert(std::is_same<mutable_pmf, without_const>::value, ""); // To add an rvalue qualifier: using rvalue_pmf = ct::add_member_rvalue_reference<pmf>; using with_rvalue = void (foo::*)(int, int&&, const int&, void*) const &&; static_assert(std::is_same<rvalue_pmf, with_rvalue>::value, ""); // Just like std::add_rvalue_reference,callable_traits::add_member_rvalue_reference // follows C++11 reference collapsing rules. While remove_member_const // and add_member_rvalue_reference are somewhat clumsy names, they are the best // the best the author could provide while still allowing both terseness // and grep-ability against std::remove_const, etc. in <type_traits>. // Naturally,CallableTraitsprovides similar tools for the other C++ // function qualifiers. Head to the reference section of this documentation // for more examples. // To remove a member pointer: using fn = ct::remove_member_pointer<pmf>; using expected_fn = void (int, int&&, const int&, void*) const; static_assert(std::is_same<fn, expected_fn>::value, ""); // We just created an abominable function type - notice the const // qualifier! namespace_scopedremove_member_const accepts abominable // types too (and so does any feature where it is legal to do so): using not_abominable = ct::remove_member_const<fn>; using expected_fn2 = void (int, int&&, const int&, void*); static_assert(std::is_same<not_abominable, expected_fn2>::value, ""); }