mirror of
https://github.com/boostorg/callable_traits.git
synced 2026-02-12 12:02:24 +00:00
ecb2f577e04dccb9a0e87af02076cdeebe34d838
CallableTraits
This project is nearing completion, but is not quite finished. Lack of documentation and spotty code quality are the most glaring issues right now, but progress is being made on both fronts.
CallableTraits provides a uniform and comprehensive interface for the type-level manipulation of all callable types in C++.
Overview
#include <type_traits>
#include <functional>
#include <tuple>
#include <callable_traits/callable_traits.hpp>
// Most of this example uses a function object. Unless otherwise noted, all
// features shown in this example can be used for any "callable" types:
// lambdas, generic lambdas, function pointers, function references,
// function types, abominable function types, member function pointers, and
// member data pointers. Ambiguous callables (e.g. function objects with
// templated/overloaded operator()) are not addressed in this example, but
// are recognized and handled by CallableTraits.
// Note: For more information about abominable function types, see Alisdair Meredith's
// proposal at http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0172r0.html
struct foo {
void operator()(int, int&&, const int&, void* = nullptr) const {}
};
namespace ct = callable_traits;
using namespace std::placeholders;
int main() {
// indexed argument types
using second_arg = ct::arg_at<1, foo>;
static_assert(std::is_same<second_arg, int&&>{}, "");
// arg types are packaged into std::tuple, which serves as the default
// type list in CallableTraits (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>{}, "");
//callable_traits::result_of is a bit friendlier than std::result_of
using return_type = ct::result_of<foo>;
static_assert(std::is_same<return_type, void>{}, "");
// callable_traits::signature yields a plain function type
using signature = ct::signature<foo>;
using expected_signature = void(int, int&&, const int&, void*);
static_assert(std::is_same<signature, expected_signature>{}, "");
// when trait information can be conveyed in an integral_constant,
// callable_traits uses constexpr functions instead of template aliases.
// Note: min_arity and max_arity only differ logically from arity when
// the argument is a function object.
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 besides can_invoke and bind_expr, which
// model std::invoke and std::bind, respectively (more on these below).
// Here's an example of the non-deduced functions, which take an explicit
// type argument. We'll ignore these for the rest of the example.
static_assert(ct::arity<foo>() == 4, "");
static_assert(ct::max_arity<foo>() == 4, "");
static_assert(ct::min_arity<foo>() == 3, "");
// C-style varargs (ellipses in a signature) can be detected.
static_assert(!ct::has_varargs(foo{}), "");
// callable_traits::is_ambiguous yields std::true_type
// only when the callable is overloaded or templated.
static_assert(!ct::is_ambiguous(foo{}), "");
// callable_traits::can_invoke allows us to preview whether
// std::invoke will compile with the given arguments. Keep
// in mind that failing cases must be SFINAE-friendly (i.e.
// any failing static_asserts can still be tripped). Note: The
// same sfinae restrictions apply to min_arity and max_arity
// for function objects.
int i = 0;
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);
// callable_traits::bind_expr is a compile-time bind expression parser,
// very loosely based on the Boost.Bind implementation. Nested bind
// expressions are fully supported. The return type of bind_expr only
// contains type information, but can still be used in an evaluated
// context. The return type can be treated like a callable type when passed
// to result_of, signature, args, or arg_at template aliases.
using bind_expression = decltype(ct::bind_expr(foo{}, _1, _1, _1));
// Unfortunately, we can't do type manipulations with std::bind directly,
// because the ISO C++ standard says very little about the return type of
// std::bind. The purpose of callable_traits::bind_expr is to undo some of
// the arbitrary black-boxing that std::bind incurs.
auto bind_obj = std::bind(foo{}, _1, _1, _1);
// Here, int is chosen as the expected argument for the bind expression
// because it's the best fit for all three placeholder slots. Behind
// the scenes, this is determined by a cartesian product of conversion
// combinations of the known parameter types represented by the reused
// placeholders (yes, this is slow). The type with the highest number of
// successful conversions "wins". int is chosen over int&& because
// non-reference types are preferred in the case of a tie.
static_assert(std::is_same<
ct::args<bind_expression>,
std::tuple<int>
>{}, "");
// callable_traits can facilitate the construction of std::function objects.
auto fn = std::function<ct::signature<bind_expression>>{ bind_obj };
fn(0);
// 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
// qualifier 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_reference_qualified(foo{}), "");
static_assert(!ct::is_rvalue_reference_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.
using pmf = decltype(&foo::operator());
{
// So that you don't have to scroll back up to see, here's the type of pmf:
using expected_pmf = void (foo::*)(int, int&&, const int&, void*) const;
static_assert(std::is_same<pmf, expected_pmf>{}, "");
}
{
// Let's remove the const qualifier:
using mutable_pmf = ct::remove_const_qualifier<pmf>;
using expected_pmf = void (foo::*)(int, int&&, const int&, void*) /*no const!*/;
static_assert(std::is_same<mutable_pmf, expected_pmf>{}, "");
}
{
// Now let's add an rvalue qualifier (&&):
using rvalue_pmf = ct::add_rvalue_qualifier<pmf>;
using expected_pmf = void (foo::*)(int, int&&, const int&, void*) const &&;
static_assert(std::is_same<rvalue_pmf, expected_pmf>{}, ""); // ^^^^
}
// You get the picture. CallableTraits lets you add and remove all PMF
// qualifiers (const, volatile, &, &&, and any combination thereof).
// These type operations can be performed on abominable function types as well.
// Somehow, somewhere, there was a sad programmer who wanted to
// manipulate c-style varargs in a function signature. There's
// really no way to do this universally without doing all the work
// that CallableTraits already does, so CallableTraits includes in
// an add/remove feature for this, too.
{
using varargs_pmf = ct::add_varargs<pmf>;
using expected_pmf = void (foo::*)(int, int&&, const int&, void*, ...) const;
static_assert(std::is_same<varargs_pmf, expected_pmf>{}, ""); // ^^^
// note: MSVC likely requires __cdecl for a varargs PMF on your
// machine, at least if you intend to do anything useful with it.
}
return 0;
}
License
Please see LICENSE.md.
Description
Languages
C++
97.6%
CMake
2.1%
Shell
0.2%
HTML
0.1%