+ The simplest way to use CallableTraits is to include the
+ main header file:
+
+
+ #include<callable_traits/callable_traits.hpp>
+
+
+ CallableTraits interface is also broken down by trait into
+ individual header files. To use only the traits you need, include one or more
+ of the following headers, listed alphabetically:
+
#include<type_traits>
+#include<callable_traits/callable_traits.hpp>
+
+namespacect=callable_traits;
+
+// For the purpose of this example, 'Bad' represents
+// any non-arithmetic type
+structBad{};
+Badbad{};
+
+// non-generic callables, such as `subtract` below,
+// don't have any "gotchas" when passed to
+// callable_traits::can_invoke.
+autosubtract=[](intx,inty){
+ returnx+y;
+};
+
+static_assert(!ct::can_invoke(subtract),"");
+static_assert(!ct::can_invoke(subtract,1),"");
+static_assert(ct::can_invoke(subtract,1,2),"");// <-- success
+static_assert(!ct::can_invoke(subtract,1,2,3),"");
+static_assert(!ct::can_invoke(subtract,bad,bad),"");
+
+// Generic function objects (such as `add` and `multiply` below)
+// must be SFINAE-friendly in order for failing can_invoke calls
+// to actually compile. This applies to both generic lambdas and
+// templated function objects. Note: MSVC does not compile this
+// source code file because of the trailing return type below (MSVC
+// is non-conforming in this respect)
+
+autoadd=[](autox,autoy)->decltype(x+y){
+ returnx+y;// ^^^^^^^^^^^^^^^^^^ explicit return type allows SFINAE
+};
+
+template<intI>
+usingint_c=std::integral_constant<int,I>;
+
+static_assert(!ct::can_invoke(add),"");
+static_assert(!ct::can_invoke(add,1),"");
+static_assert(ct::can_invoke(add,1,2),"");// <-- success
+static_assert(!ct::can_invoke(add,1,2,3),"");
+static_assert(!ct::can_invoke(add,bad,bad),"");
+
+// 'multiply' isn't SFINAE-safe, because the return type depends
+// on an expression in the body. However, it still works if we
+// only try to call can_invoke with arguments where x * y is
+// a valid expression.
+automultiply=[](autox,autoy){
+ returnx*y;
+};
+
+
+static_assert(!ct::can_invoke(multiply),"");
+static_assert(!ct::can_invoke(multiply,1),"");
+static_assert(ct::can_invoke(multiply,1,2),"");// <-- success
+static_assert(!ct::can_invoke(multiply,1,2,3),"");
+//static_assert(!ct::can_invoke(multiply, bad, bad), "");
+
+// The last, commented static_assert above would fail compile,
+// because the call cannot be SFINAE'd away. Error message in Clang:
+// "invalid operands to binary expression ('Bad' and 'Bad')"
+
+intmain(){}
+
#include<callable_traits/callable_traits.hpp>
+
+namespacect=callable_traits;
+
+structfoo{
+ intbar(int)const{
+ return1;
+ }
+};
+
+// can_invoke returns std::true_type here because the
+// arguments are valid to INVOKE
+static_assert(ct::can_invoke(&foo::bar,foo{},0),"");
+
+// can_invoke returns std::false_type here because the
+// arguments are NOT valid to INVOKE - foo::bar can't be
+// invoked like a void member function.
+static_assert(!ct::can_invoke(&foo::bar,foo{}),"");
+
+intmain(){}
+
#include<callable_traits/callable_traits.hpp>
+
+namespacect=callable_traits;
+
+intfoo(int&&i){
+ returni;
+}
+
+// can_invoke returns std::true_type here because the
+// arguments are valid to INVOKE
+static_assert(ct::can_invoke(&foo,0),"");
+
+inti=0;
+
+// can_invoke returns std::false_type here because the
+// arguments are NOT valid to INVOKE - foo expects an
+// rvalue reference, not an lvalue reference.
+static_assert(!ct::can_invoke(&foo,i),"");
+
+intmain(){}
+
#include<callable_traits/callable_traits.hpp>
+
+namespacect=callable_traits;
+
+intfoo(int&&i){
+ returni;
+}
+
+// can_invoke returns std::true_type here because the
+// arguments are valid to INVOKE
+static_assert(ct::can_invoke(foo,0),"");
+
+inti=0;
+
+// can_invoke returns std::false_type here because the
+// arguments are NOT valid to INVOKE - foo expects an
+// rvalue reference, not an lvalue reference.
+static_assert(!ct::can_invoke(foo,i),"");
+
+intmain(){}
+
#include<type_traits>
+#include<callable_traits/callable_traits.hpp>
+
+// NOTE: Due to non-compliance in MSVC, can_invoke_constexpr
+// always return std::false_type on that compiler, which causes
+// the static asserts below to fail.
+
+namespacect=callable_traits;
+
+usingT1=std::integral_constant<int,3>;
+usingT2=std::integral_constant<int,7>;
+
+//'subtract' is a constexpr function object that
+// subtracts std::integral_constant types.
+structsubtract{
+
+ // To compile failing cases of can_invoke_constexpr, the function object
+ // must have a SFINAE-safe signature. In this case, 'subtract' is made
+ // SFINAE-safe with an explicit, trailing return type.
+ template<typenameT1,typenameT2>
+ constexprautooperator()(T1,T2)const->decltype(T1::value-T2::value){
+ returnT1::value-T2::value;
+ }
+};
+
+// can_invoke_constexpr returns std::true_type in the first case, because
+// INVOKE(subtract{}, T1{}, T2{}) is a valid expression, AND 'subtract{}' is
+// a constexpr function object.
+static_assert(ct::can_invoke_constexpr(subtract{},T1{},T2{}),"");
+static_assert(!ct::can_invoke_constexpr(subtract{},3,7),"");
+static_assert(!ct::can_invoke_constexpr(subtract{},T1{}),"");
+
+//this is a function object, but is NOT constexpr
+structadd{
+ template<typenameT1,typenameT2>
+ autooperator()(T1,T2)const->decltype(T1::value+T2::value){
+ returnT1::value+T2::value;
+ }
+};
+
+// Even though INVOKE(add{}, T1{}, T2{}) is valid, the respective
+// can_invoke_constexpr call returns std::false_type because 'add{}'
+// is not a constexpr function object.
+static_assert(!ct::can_invoke_constexpr(add{},T1{},T2{}),"");
+static_assert(!ct::can_invoke_constexpr(add{},3,7),"");
+
+
+// This last section demonstrates that can_invoke_constexpr will always
+// return std::false_type when any of the arguments do not decay to literal
+// types. (see http://en.cppreference.com/w/cpp/concept/LiteralType).
+// Even though 'S' below is a constexpr function object, it is incompatible
+// with can_invoke_constexpr because 'S' isn't a literal type. Additionally,
+// all arguments must be default constructible.
+
+structS{
+ S()=delete;
+ S(int){};
+ constexprintoperator()()const{return0;}
+};
+
+Ss{0};
+static_assert(!ct::can_invoke_constexpr(s),"");
+
+
+intmain(){}
+
#include<type_traits>
+#include<callable_traits/callable_traits.hpp>
+
+// NOTE: Due to non-compliance in MSVC, can_invoke_constexpr
+// always returns std::false_type on that compiler, which
+// causes a static assert below to fail.
+
+namespacect=callable_traits;
+
+structfoo{
+ constexprintbar(int)const{
+ return1;
+ }
+};
+
+usingpmf_constant=std::integral_constant<decltype(&foo::bar),&foo::bar>;
+
+// can_invoke_constexpr returns true here because foo::bar
+// is constexpr, and the arguments are valid to INVOKE
+static_assert(ct::can_invoke_constexpr(pmf_constant{},foo{},0),"");
+
+// can_invoke_constexpr returns false here because even though
+// foo::bar is constexpr, the arguments do not obey INVOKE rules
+static_assert(!ct::can_invoke_constexpr(pmf_constant{},foo{}),"");
+
+intmain(){}
+
#include<type_traits>
+#include<callable_traits/callable_traits.hpp>
+
+namespacect=callable_traits;
+
+constexprintseven(int){
+ return7;
+}
+
+usingseven_c=std::integral_constant<decltype(&seven),&seven>;
+
+// The first call to can_invoke_constexpr returns std::true_type
+// because `seven` is a constexpr function, and valid INVOKE arguments
+// are passed. The second call to can_invoke_constexpr returns
+// std::false_type, because the arguments are not valid to INVOKE
+static_assert(ct::can_invoke_constexpr(seven_c{},0),"");
+static_assert(!ct::can_invoke_constexpr(seven_c{},nullptr),"");
+
+inteight(int){
+ return7;
+}
+
+usingeight_c=std::integral_constant<decltype(&eight),&eight>;
+
+// `eight` is NOT a constexpr function, so can_invoke_constexpr
+// returns `std::false_type` even for valid INVOKE arguments.
+static_assert(!ct::can_invoke_constexpr(eight_c{},0),"");
+static_assert(!ct::can_invoke_constexpr(eight_c{},nullptr),"");
+
+intmain(){}
+
+ When compiling in MSVC, is_constexpr
+ always returns std::false_type, because MSVC cannot compile
+ the logic that normally determines this.
+
+
+
#include<type_traits>
+#include<callable_traits/callable_traits.hpp>
+
+namespacect=callable_traits;
+
+//this is a constexpr function object (non-templated)
+structzero{
+
+ constexprautooperator()()const{
+ return0;
+ }
+};
+
+static_assert(ct::is_constexpr<zero>(),"");
+static_assert(ct::is_constexpr(zero{}),"");
+
+
+
+//this is a constexpr function object (templated)
+structsubtract{
+
+ // For callable_traits::is_constexpr, generic function objects
+ // only need to be SFINAE-friendly if the body of the operator()
+ // function accesses member names besides "type" and "value".
+ // Unary/binary operators and constructor calls are okay to use.
+ template<typenameT1,typenameT2>
+ constexprautooperator()(T1,T2)const{
+ returnT1{}-T2{};
+ }
+};
+
+static_assert(ct::is_constexpr<subtract>(),"");
+static_assert(ct::is_constexpr(subtract{}),"");
+
+
+
+//this is NOT a constexpr function object
+structadd{
+ template<typenameT1,typenameT2>
+ autooperator()(T1,T2)const{
+ returnT1{}+T2{};
+ }
+};
+
+static_assert(!ct::is_constexpr<add>(),"");
+static_assert(!ct::is_constexpr(add{}),"");
+
+automultiply=[](autot1,autot2)->decltype(t1.value*t2.value){
+ returnt1.value*t2.value;
+};
+
+static_assert(!ct::is_constexpr<decltype(multiply)>(),"");
+static_assert(!ct::is_constexpr(multiply),"");
+
+
+// is_constexpr will always return std::false_type when the argument
+// is either not a literal type, or is not default constructible. Below,
+// divide is not default constructible, so is_constexpr returns
+// std::false_type. For literal types that are default constructible, a
+// constexpr default constructor is assumed.
+
+structdivide{
+
+ divide()=delete;
+ constexprdivide(int){};
+
+ template<typenameT1,typenameT2>
+ constexprautooperator()(T1,T2)const{
+ returnT1{}/T2{};
+ }
+};
+
+static_assert(!ct::is_constexpr<divide>(),"");
+static_assert(!ct::is_constexpr(divide{0}),"");
+
+intmain(){}
+
#include<type_traits>
+#include<callable_traits/callable_traits.hpp>
+
+// NOTE: Due to non-compliance in MSVC, is_constexpr always
+// returns std::false_type on that compiler, which causes
+// the static asserts below to fail.
+
+namespacect=callable_traits;
+
+constexprintfoo(constint&){
+ return1;
+}
+
+intbar(constint&){
+ return1;
+}
+
+// for is_constexpr calls, function pointers must
+// be passed as std::integral_constants
+usingF=std::integral_constant<decltype(&foo),&foo>;
+usingB=std::integral_constant<decltype(&bar),&bar>;
+
+static_assert(ct::is_constexpr(F{}),"");
+static_assert(ct::is_constexpr<F>(),"");
+static_assert(!ct::is_constexpr(B{}),"");
+static_assert(!ct::is_constexpr<B>(),"");
+
+intmain(){}
+
- CallableTraits contains the following type traits and metafunctions (psuedo-code):
-
-
- std::integral_constant constexpr functions
-
-
-
- can_invoke(T&&,
- Args&&...)
-
-
- can_invoke_constexpr(T&&,
- Args&&...)
-
-
- is_constexpr(T&&)
-
-
- is_constexpr<T>()
-
-
- has_varargs(T&&)
-
-
- has_varargs<T>()
-
-
- has_void_return(T&&)
-
-
- has_void_return<T>()
-
-
- arity(T&&)
-
-
- arity<T>()
-
-
- min_arity(T&&)
-
-
- min_arity<T>()
-
-
- max_arity(T&&)
-
-
- max_arity<T>()
-
-
- is_unqualified(T&&)
-
-
- is_unqualified<T>()
-
-
- is_const_qualified(T&&)
-
-
- is_const_qualified<T>()
-
-
- is_volatile_qualified(T&&)
-
-
- is_volatile_qualified<T>()
-
-
- is_cv_qualified(T&&)
-
-
- is_cv_qualified<T>()
-
-
- is_reference_qualified(T&&)
-
-
- is_reference_qualified<T>()
-
-
- is_lvalue_qualified(T&&)
-
-
- is_lvalue_qualified<T>()
-
-
- is_rvalue_qualified(T&&)
-
-
- is_rvalue_qualified<T>()
-
-
-
- Template aliases
-
-
-
- args<T>
-
-
- arg_at<I,T>
-
-
- function_type<T>
-
-
- qualified_function_type<T>
-
-
- result_of<T>
-
-
- apply_return<T,R>
-
-
- apply_member_pointer<T,C>
-
-
- remove_member_pointer<T>
-
-
- add_const_qualifier<T>
-
-
- remove_const_qualifier<T>
-
-
- add_volatile_qualifier<T>
-
-
- remove_volatile_qualifier<T>
-
-
- add_cv_qualifiers<T>
-
-
- remove_cv_qualifiers<T>
-
-
- add_lvalue_qualifier<T>
-
-
- add_rvalue_qualifier<T>
-
-
- remove_reference_qualifiers<T>
-
-
- add_varargs<T>
-
-
- remove_varargs<T>
-
-
-
- Other
-
-
- bind(T&&,
- Args&&...)
-
-
- Note: CallableTraits currently offers no interface for the manipulation of
- calling conventions. Also, no features are currently implemented to account
- for C++17's noexcept, but will
- be added in future versions for supported compilers.
-
-
- The simplest way to use CallableTraits is to include the main header file:
-
-
#include<callable_traits/callable_traits.hpp>
-
-
- CallableTraits interface is also broken down by trait into individual header
- files. To use only the traits you need, include one or more of the following
- headers, which are listed alphabetically:
-
#include<type_traits>
+#include<functional>
+#include<tuple>
+#include<callable_traits/callable_traits.hpp>
+
+structfoo{
+ voidoperator()(int,int&&,constint&,void*=nullptr)const{}
+};
+
+namespacect=callable_traits;
+usingnamespacestd::placeholders;
+
+intmain(){
+
+ // indexed argument types
+ usingsecond_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 in CallableTraits (runtime capabilities are not used).
+ usingargs=ct::args<foo>;
+ usingexpected_args=std::tuple<int,int&&,constint&,void*>;
+ static_assert(std::is_same<args,expected_args>::value,"");
+
+ //callable_traits::result_of is a bit friendlier than std::result_of
+ usingreturn_type=ct::result_of<foo>;
+ static_assert(std::is_same<return_type,void>::value,"");
+
+ //has_void_return is a quicker way to perform the check above
+ static_assert(ct::has_void_return(foo{}),"");
+
+ // callable_traits::function_type decays a callable type to
+ // a plain function type, which is structured in terms of INVOKE
+ usingfunction_type=ct::function_type<foo>;
+ usingexpected_function_type=void(int,int&&,constint&,void*);
+ static_assert(std::is_same<function_type,expected_function_type>::value,"");
+
+ // when trait information can be conveyed in an std::integral_constant,
+ // callable_traits opts for constexpr functions instead of template aliases.
+ // This is done to encourage value semantics, and to simplify usage inside
+ // of forwarding functions.
+ static_assert(ct::arity(foo{})==4,"");
+ static_assert(ct::max_arity(foo{})==4,"");
+ static_assert(ct::min_arity(foo{})==3,"");
+
+ // CallableTraits provides constexpr functions so that the user doesn't
+ // need to worry about reference collapsing or decltype when dealing with
+ // universal references to callables. Still, you don't NEED an instance,
+ // because CallableTraits provides non-deduced function templates for
+ // all constexpr functions (except for can_invoke/can_invoke_constexpr and bind,
+ // which model std::invoke and std::bind, respectively -- more on these below).
+ // Here's an example of the non-deduced version of arity, which take an
+ // explicit type argument. We'll ignore these non-deduced overloads for the
+ // rest of this example.
+ static_assert(ct::arity<foo>()==4,"");
+
+ // C-style variadics detection (ellipses in a signature)
+ static_assert(!ct::has_varargs(foo{}),"");
+
+ inti=0;
+
+ // callable_traits::can_invoke allows us to preview whether std::invoke will
+ // compile with the given arguments.
+ static_assert(ct::can_invoke(foo{},0,0,i),"");
+ // no error: std::invoke(foo{}, 0, 0, i);
+
+ static_assert(!ct::can_invoke(foo{},nullptr),"");
+ // error: std::invoke(foo{}, nullptr);
+
+ // For function objects, the following checks are determined by the
+ // qualifiers on operator(), rather than the category of the passed value.
+ // For member function pointers and abominable function types, the
+ // qualifiers on the function type are used.
+ static_assert(ct::is_const_qualified(foo{}),"");
+ static_assert(!ct::is_volatile_qualified(foo{}),"");
+ static_assert(!ct::is_reference_qualified(foo{}),"");
+ static_assert(!ct::is_lvalue_qualified(foo{}),"");
+ static_assert(!ct::is_rvalue_qualified(foo{}),"");
+
+
+ // If you find yourself in the unfortunate situation of needing
+ // to manipulate member function pointer types, CallableTraits
+ // has all the tools you need to maintain your sanity.
+
+
+ usingpmf=decltype(&foo::operator());
+
+ // So that you don't have to scroll back up to see, here's the type of pmf:
+ usingwith_const=void(foo::*)(int,int&&,constint&,void*)const;
+ static_assert(std::is_same<pmf,with_const>::value,"");
+
+ // Let's remove the const qualifier:
+ usingmutable_pmf=ct::remove_const_qualifier<pmf>;
+ usingwithout_const=void(foo::*)(int,int&&,constint&,void*)/*no const!*/;
+ static_assert(std::is_same<mutable_pmf,without_const>::value,"");
+
+ // Now let's add an rvalue qualifier (&&):
+ usingrvalue_pmf=ct::add_rvalue_qualifier<pmf>;
+ usingwith_rvalue=void(foo::*)(int,int&&,constint&,void*)const&&;
+ static_assert(std::is_same<rvalue_pmf,with_rvalue>::value,"");
+
+ // You get the picture. CallableTraits lets you add and remove all PMF
+ // qualifiers (const, volatile, &, &&, and any combination thereof).
+ // These type operations can generally be performed on abominable function
+ // types as well.
+
+ // is_constexpr would return std::true_type if foo's operator() were constexpr.
+ static_assert(!ct::is_constexpr(foo{}),"");
+
+ // to check constexprness of a function or member function, you must use an
+ // std::integral_constant, like this:
+ usingpmf_constant=std::integral_constant<pmf,&foo::operator()>;
+ static_assert(!ct::is_constexpr(pmf_constant{}),"");
+}
+
- CallableTraits provides a comprehensive, uniform, and modern type-level interface
- for the manipulation and inspection of callable types in C++. By filling the
- gaps where existing library solutions fall short, CallableTraits aims to provide
- such an exhaustive interface for the features listed above that libary writers
- will never again need to specialize templates
- for callable types. With CallableTraits, library writers should never need
- to write a template like this one:
-
+ CallableTraits provides a comprehensive, uniform, and
+ modern type-level interface for the manipulation and inspection of callable
+ types in C++. By filling the gaps where existing library solutions fall short,
+ CallableTraits aims to provide such an exhaustive interface
+ for the features listed above that library writers will never
+ again need to specialize templates for callable types. CallableTraits
+ eliminates the need for horrific template specializations like these:
+
- Several library solutions exist to manipulate these types, or to abstract away
- their complexities. However, these solutions are occasionally inflexible or
- lacking in features, especially regarding function objects/lambdas. In CallableTraits,
- function pointers, function references, function types, abominable function
- types, member function pointers, member data pointers, function objects/lambdas,
- and references/pointers/smart pointers thereof are generally interchangeable.
-
+ Several library solutions exist to manipulate these types, or to abstract
+ away their complexities. However, these solutions are occasionally inflexible
+ or lacking in features, especially regarding function objects/lambdas. In
+ CallableTraits, function pointers, function references,
+ function types, abominable function types, member function pointers, member
+ data pointers, function objects/lambdas, and references/pointers/smart pointers
+ thereof are generally interchangeable.
+
+ CallableTraits currently offers no interface for the
+ manipulation of calling conventions. Also, no features are currently implemented
+ to account for C++17's noexcept.
+ These features are planned for future versions on supported platforms.
+
- CallableTraits' template aliases and constexpr
- std::integral_constant functions are preferable
- to typenamefoo::type
- and foo::value
+ CallableTraits' template aliases and constexprstd::integral_constant
+ functions are preferable to typename
+ foo::type and foo::value
- CallableTraits offers a familar, consistent interface for the synthesis
- and decomposition of callable types
+ CallableTraits offers a familar, consistent interface
+ for the synthesis and decomposition of callable types
- e.g. callable_traits::remove_const_qualifier
+ e.g. remove_const_qualifier
parallels std::remove_const
At the time of this writing, there are 260 search
results on Stack Overflow concerning the conversion from std::bind
to std::function. There are 336 search
results concerning the conversion of lambdas to std::function.
With this example, we'll kill both birds with the same stone. The make_function function defined below will
- leverage CallableTraits to...
+ leverage CallableTraits to...
@@ -202,9 +387,9 @@
Without real-world context, make_function
- may seem rather silly to those who are aware of the runtime costs of type
- erasure. However, whenever std::function
- is required by some 3rd party API, this example makes a bit more sense.
+ may seem rather silly to those who know the runtime costs of type erasure.
+ However, whenever std::function is required by some 3rd party
+ API, this example might make a bit more sense.
#include<functional>#include<callable_traits/callable_traits.hpp>
@@ -213,11 +398,11 @@
namespacect=callable_traits;
- //`make_function` turns a non-overloaded callable into a type-erased std::function object
+ //make_function turns a non-overloaded callable into a type-erased std::function objecttemplate<typenameT>inlinedecltype(auto)make_function(T&&t){
- // callable_traits::function_type decays any non-overloaded callable type to
+ // callable_traits::function_type decays any non-overloaded callable type to// a plain function type, which is structured in terms of INVOKE.usingsignature=ct::function_type<T&&>;
@@ -225,7 +410,7 @@
returnresult_type{std::forward<T>(t)};}
- //this `make_function` overload turns a bind expression into a type-erased std::function object
+ //this make_function overload turns a bind expression into a type-erased std::function objecttemplate<typenameT,typenameFirst,typename...Others>inlinedecltype(auto)make_function(T&&t,First&&first,Others&&...others){
@@ -235,22 +420,22 @@
// determine the de-facto signature of the std::bind return type, with special// considerations for conversions between reused placeholders and nested// placeholder expressions. For the sake of convenience, callable_traits::bind
- // is also a thin forwarding wrapper around std::bind (which is the only true
+ // is also a thin forwarding wrapper around std::bind (which is the only true// runtime element in CallableTraits).usingbind_expr=decltype(ct::bind(
- std::forward<T>(t),
- std::forward<First>(first),
- std::forward<Others>(others)...
+ std::forward<T>(t),
+ std::forward<First>(first),
+ std::forward<Others>(others)...));usingsignature=ct::function_type<bind_expr>;usingresult_type=std::function<signature>;returnresult_type{std::bind(
- std::forward<T>(t),
- std::forward<First>(first),
- std::forward<Others>(others)...
+ std::forward<T>(t),
+ std::forward<First>(first),
+ std::forward<Others>(others)...)};}}
@@ -302,6 +487,6 @@