Complete reflection of C++ function types and modification of their properties.
Anatomy of a general C++17 function type:
template <typename R, typename... P, bool X>
using
function = R(P...[,...]) [const] [volatile] [&|&&] noexcept(X);
Dissected - a breakdown of the general type, with library API terminology
'
A
|B
' forA
orB
alternatives - '[C
]' for optionalC
term:
/*
_signature_ ________cvref________ noexcept_ *
| | | | | | */
function = R(P...[,...]) [const] [volatile] [&|&&] noexcept(X); /*
| |__| |__| |_____ _______| | | *
return_type | variadic cv reference *
arg_types lvalue | rvalue */
Function signature (all API terms in bold):
R(P...)
|R(P...,...)
: signature = return_typeR
and arg_typesP...
Here, 'signature' refers to return_type
R
and arg_types (parameter)P...
including any C-style varargs (termed 'variadic', denoted by trailing ellipsis...
)
excluding everything after the function parens (i.e. no cvref or exception spec).
Function varargs existence is treated as a (bool
) property for API purposes:
- variadic : API property name for presence of ellipsis: true | false
Function noexcept property (bool
):
noexcept(X)
: Function exception specification; X = true | false
Function cvref properties (bool
, bool
, ref_qual
):
- [
const
] [volatile
] [&
|&&
] : Function cvref qualifiers; 12 combos
Warning: the cvref API terms may be familiar from the
std
traits but have
different meanings and behaviour as function type qualifiers (see API refs):
- const, volatile, cv (const | volatile)
- reference_lvalue, reference_rvalue, reference (lval | rval)
- cvref (const | volatile | reference)
The cvref qualifiers divide the function types into two top level categories:
- free function types, with no cvref qualifiers - the valid types of free functions
- cvref qualified function types, the so-called 'abominable' function types
Test with function traits is_free_function<T>
or function_is_cvref<F>
Copyright © 2019 Will Wray. Distributed under the Boost Software License, V1.0
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:
The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
Also at boost.org and accompanying file LICENSE_1_0.txt
Background: C++ function types, the free & the abominable (P0172)
-
C++ function types
The
std
type traitis_function_v<F>
is true for all C++ function types.C++ function types include the types of ordinary C/C++ free functions,
referred to here as 'free' function types:// free function types void(int) or auto(int) -> void char*() noexcept or auto() noexcept -> char* int(char const*,...) or auto(char const*,...) -> int
C++ function types can also have cvref qualifiers:
int() const& or auto() const& -> int void() && noexcept or auto() && noexcept -> void void(int) volatile or auto(int) volatile -> void
Such cvref-qualified function types are an artifact of the C++ type system.
Member functions carry cvref qualifiers for the implicit*this
reference
used in calling the member function, so cvref-qualified function types arise
as part of pointer-to-member-function types.
You cannot declare an ordinary free function with a cvref type and it is
forbidden to form a pointer or a reference to a cvref-qualified function type. -
P0172R0 Abominable Function Types by Alisdair Meredith, Nov 2015
Quoting from P0172R0 section 2.1, Definition:
[...] an abominable function type is the type produced by writing
a function type followed by a cv-ref qualifier.Example:
using regular = void(); using abominable = void() const volatile &&;
In the example above,
regular
names a familiar function type [...],
abominable
also names a function type, not a reference type, and
despite appearances, is neither a const nor a volatile qualified type.
There is no such thing as a cv-qualified function type in the type system,
and the abominable function type is something else entirely. -
Boost.CallableTraits: A P0172 implementation and more
Boost.CallableTraits implements P0172R0's suggested library interface,
extended to support general Callable types on top of C++ function types.
It is a robust, reviewed library with tests, compatibility matrix and CI.
Description
- Type trait:
a template-based interface to query or modify the properties of types.
function_traits
is a library of traits for C++17 function types -
no more, no less; it does not provide traits for general Callable types
(function traits can ease implementation of facilities like callable traits).It depends on std
<type_traits>
which it complements with function traits.
The library usesnamespace ltl
for its traits, types and functions.It targets C++17 on recent gcc / clang / msvc compilers.
Backwards compatibility, for older compilers or for pre-17, is not a priority.
It is an 'alpha' design with an experimental interface, subject to change.
Once C++20 is available, constraints will be added.
Motivation: Provide the 24 (or 48) required specializations
See also Boost.CallableTraits Motivation
Function traits are necessary to reflect the properties of function types.
They may be useful in generic code that must handle general function types.
'Abominable' function cvref qualifiers cannot be deduced concisely.
C-style varargs - a trailing ellipsis ... - cannot be deduced concisely.
A total of 24 separate template specializations are needed to match
a possibly abominable or variadic function type:
- 12 combinations of cvref qualifiers (4 cv x 3 ref)
- x 2 for presence of C-style varargs (trailing ellipsis...)
If
noexcept
is not deduced directly then 48 specializations are needed:
- x 2 for
noexcept
true or false
It is tedious to have to write all of the necessary specializations.
This library provides the specializations wrapped up as function traits.Since all 24/48 specializations are needed to implement any function trait
with full generality, one might as well write a full collection of traits.'Setter' traits
I wanted traits to copy qualifiers from source to target function types (e.g.
Boost.CallableTraits has an open issue to add acopy_member_cvref
trait
andstd::copy_*
traits are proposed in P1016 "...type manipulation utilities")This library provides a couple of options:
function_set_cvref_as<F,G>
copiesG
's cvref qualifiers toF
, or
function_set_signature<G, function_signature_t<F>>
effectively copiesG
's cvref qualifiers and exception spec toF
's signature.
The 24 (or 48) function pattern specializations
24 template specializations are required to match any function type pattern
(assuming that noexcept
is deducible in partial specializations - see note below):
// Primary template
template<typename T> struct fun;
// The 24 template partial specializations
// to match cvref qualifiers (x12) and presence of varargs (x2)
// while deducing return type R, parameters P... and noexcept(bool)
template<class R, class... P, bool X> struct fun<R(P...) noexcept(X)> {};
template<class R, class... P, bool X> struct fun<R(P...) & noexcept(X)> {};
template<class R, class... P, bool X> struct fun<R(P...) && noexcept(X)> {};
template<class R, class... P, bool X> struct fun<R(P...) const noexcept(X)> {};
template<class R, class... P, bool X> struct fun<R(P...) const & noexcept(X)> {};
template<class R, class... P, bool X> struct fun<R(P...) const && noexcept(X)> {};
template<class R, class... P, bool X> struct fun<R(P...) volatile noexcept(X)> {};
template<class R, class... P, bool X> struct fun<R(P...) volatile & noexcept(X)> {};
template<class R, class... P, bool X> struct fun<R(P...) volatile && noexcept(X)> {};
template<class R, class... P, bool X> struct fun<R(P...) const volatile noexcept(X)> {};
template<class R, class... P, bool X> struct fun<R(P...) const volatile & noexcept(X)> {};
template<class R, class... P, bool X> struct fun<R(P...) const volatile && noexcept(X)> {};
template<class R, class... P, bool X> struct fun<R(P..., ...) noexcept(X)> {};
template<class R, class... P, bool X> struct fun<R(P..., ...) & noexcept(X)> {};
template<class R, class... P, bool X> struct fun<R(P..., ...) && noexcept(X)> {};
template<class R, class... P, bool X> struct fun<R(P..., ...) const noexcept(X)> {};
template<class R, class... P, bool X> struct fun<R(P..., ...) const & noexcept(X)> {};
template<class R, class... P, bool X> struct fun<R(P..., ...) const && noexcept(X)> {};
template<class R, class... P, bool X> struct fun<R(P..., ...) volatile noexcept(X)> {};
template<class R, class... P, bool X> struct fun<R(P..., ...) volatile & noexcept(X)> {};
template<class R, class... P, bool X> struct fun<R(P..., ...) volatile && noexcept(X)> {};
template<class R, class... P, bool X> struct fun<R(P..., ...) const volatile noexcept(X)> {};
template<class R, class... P, bool X> struct fun<R(P..., ...) const volatile & noexcept(X)> {};
template<class R, class... P, bool X> struct fun<R(P..., ...) const volatile && noexcept(X)> {};
Both GCC and Clang deduce noexcept as intended...
Unfortunately, when noexcept
was introduced as part of the type system
the standard was not also updated to specify deduction of noexcept.
This oversight should be corrected by a defect report before C++2a.
Currently (start of 2019) MSVC does not deduce noexcept and so requires
the noexcept cases to be expanded via 48 specializations:
template<class R, class... P> struct fun<R(P...)> {};
template<class R, class... P> struct fun<R(P...) &> {};
template<class R, class... P> struct fun<R(P...) &&> {};
template<class R, class... P> struct fun<R(P...) const> {};
template<class R, class... P> struct fun<R(P...) const &> {};
template<class R, class... P> struct fun<R(P...) const &&> {};
template<class R, class... P> struct fun<R(P...) volatile> {};
template<class R, class... P> struct fun<R(P...) volatile &> {};
template<class R, class... P> struct fun<R(P...) volatile &&> {};
template<class R, class... P> struct fun<R(P...) const volatile> {};
template<class R, class... P> struct fun<R(P...) const volatile &> {};
template<class R, class... P> struct fun<R(P...) const volatile &&> {};
template<class R, class... P> struct fun<R(P..., ...)> {};
template<class R, class... P> struct fun<R(P..., ...) &> {};
template<class R, class... P> struct fun<R(P..., ...) &&> {};
template<class R, class... P> struct fun<R(P..., ...) const> {};
template<class R, class... P> struct fun<R(P..., ...) const &> {};
template<class R, class... P> struct fun<R(P..., ...) const &&> {};
template<class R, class... P> struct fun<R(P..., ...) volatile> {};
template<class R, class... P> struct fun<R(P..., ...) volatile &> {};
template<class R, class... P> struct fun<R(P..., ...) volatile &&> {};
template<class R, class... P> struct fun<R(P..., ...) const volatile> {};
template<class R, class... P> struct fun<R(P..., ...) const volatile &> {};
template<class R, class... P> struct fun<R(P..., ...) const volatile &&> {};
template<class R, class... P> struct fun<R(P...) noexcept> {};
template<class R, class... P> struct fun<R(P...) & noexcept> {};
template<class R, class... P> struct fun<R(P...) && noexcept> {};
template<class R, class... P> struct fun<R(P...) const noexcept> {};
template<class R, class... P> struct fun<R(P...) const & noexcept> {};
template<class R, class... P> struct fun<R(P...) const && noexcept> {};
template<class R, class... P> struct fun<R(P...) volatile noexcept> {};
template<class R, class... P> struct fun<R(P...) volatile & noexcept> {};
template<class R, class... P> struct fun<R(P...) volatile && noexcept> {};
template<class R, class... P> struct fun<R(P...) const volatile noexcept> {};
template<class R, class... P> struct fun<R(P...) const volatile & noexcept> {};
template<class R, class... P> struct fun<R(P...) const volatile && noexcept> {};
template<class R, class... P> struct fun<R(P..., ...) noexcept> {};
template<class R, class... P> struct fun<R(P..., ...) & noexcept> {};
template<class R, class... P> struct fun<R(P..., ...) && noexcept> {};
template<class R, class... P> struct fun<R(P..., ...) const noexcept> {};
template<class R, class... P> struct fun<R(P..., ...) const & noexcept> {};
template<class R, class... P> struct fun<R(P..., ...) const && noexcept> {};
template<class R, class... P> struct fun<R(P..., ...) volatile noexcept> {};
template<class R, class... P> struct fun<R(P..., ...) volatile & noexcept> {};
template<class R, class... P> struct fun<R(P..., ...) volatile && noexcept> {};
template<class R, class... P> struct fun<R(P..., ...) const volatile noexcept> {};
template<class R, class... P> struct fun<R(P..., ...) const volatile & noexcept> {};
template<class R, class... P> struct fun<R(P..., ...) const volatile && noexcept> {};
These 48 specializations are also listed in Boost.CallableTraits and cppreference is_function
Aims: A complete, minimal, forward looking, simple dependency
-
A complete yet minimal set of function type traits
Complete: provide a way to do any query or modification that may be needed;
if you see something that is not reasonably easy to do then open an issue.Minimal: avoid bloat and duplication in the interface (not easy - 50 traits!).
Narrow scope, single responsibility - function traits only, no more, no less. -
In a single header, simple to take as a dependency
Simple dependency: single header, self contained with docs.
Mesonbuild example as subproject / git submodule. CMake ToDo.
Of course, you can just copy the header or cut-n-paste.Single header: rather than 'fine-grain' headers per trait.
Because each trait has to pull in the full 24 (or 48) specializations,
even if a user may only want one of the many traits,
it seems not worth the complexity of providing individual headers
(if you can show benefits worth the complexity then open an issue). -
Forward looking: to concepts - down with SFINAE!
Look towards concepts and contraints with no need for SFINAE tricks
No concern for backward compatibility or support of old compilers
Diverge from the P0172R0 suggested interface as appropriate
A clean, modern implementation (macro use confined to header).
Getting started
First, put the header file where you want it and configure your include path.
Here, theltl
include directory reflectsfunction_traits
namespace,ltl
(or, just cut and paste the header):
#include <ltl/function_traits.hpp>
All
function_*
traits are defined only for function types.
Calling afunction_*
trait with a non function type gives a hard,
non-SFINAE, error (with a nasty error message from the compiler).
ltl::function_is_cvref< int > // compile error
The
function_is_*
predicate traits have SFINAE-friendly siblings:
ltl::is_function_cvref< int > // empty class
Other
function_*
traits have no safe / SFINAE-friendly variants.
To use these function traits with non-function types, you can guard the trait
instantiation withif constexpr (is_function_v<T>)
:
template <typename F>
inline constexpr bool is_free_function_v = []{
if constexpr (is_function_v<F>)
return !function_is_cvref_v<F>;
return false; }();
(
conditional_t
doesn't work here as both branches instantiate.)
Predicate traits and type property traits
Test if a function type is variadic and const and noexcept:
using Fc = void(...) const noexcept;
static_assert(
ltl::function_is_variadic_v< Fc >
&& ltl::function_is_const_v< Fc >
&& ltl::function_is_noexcept_v< Fc >
);
Get the return type of a function type and a type-list of its parameter types:
#include <tuple>
#include <type_traits>
using Fcb = void( char, bool() );
static_assert(
std::is_void_v< ltl::function_return_type_t< Fcb > >
&& std::is_same_v< ltl::function_arg_types< Fcb, std::tuple >
, std::tuple< char, bool(*)() > >
); // ^^^
// note decay
Modifying traits; add / remove and set traits
Conventional
add_*
,remove_*
traits modify the given property*
.
They generally take no arguments beyond the function type to modify:
using namespace ltl;
static_assert(
std::is_same_v< function_add_const_t<void() &>,
void() const& >
&& std::is_same_v< function_remove_cvref_t<void() const &>,
void() >
);
Some property traits act as
remove_*
traits; the 'signature' property trait
effectively removes both cvref and noexcept:
static_assert(
std::is_same_v< function_signature_t<void() & noexcept>,
void() >
);
set_*
traits are more programmatic thanadd_*
andremove_*
traits.
Setters for function cv qualifiers, noexcept and variadic takebool
arguments:
static_assert( function_is_noexcept_v<
function_set_noexcept_t<void(), true> >);
The
set
trait for reference qualifiers takes altl::ref_qual
argument
(an enum type with valueslval_ref_v
,rval_ref_v
ornull_ref_v
)
static_assert(
std::is_same_v< function_set_reference_t<void() &, rval_ref_v>,
void() && >
);
Reference collapse is not necessarily natural for function reference qualifiers.
If you need it,function_add_reference<F,R>
does reference collapse
static_assert(
std::is_same_v< function_add_reference_t<void() &, rval_ref_v>,
void() & >
);
('adding' an rvalue-ref to an lvalue-ref yields an lvalue-ref, consistent with
std::add_rvalue_reference
for ordinary reference types;&
+&&
=>&
)Setters for type properties take type arguments; to change function return type:
static_assert(
std::is_same_v< function_set_return_type_t<int(), void>,
void() >);
The
set_cvref_as
trait provides a way to copy qualifiers to the target function type
from a source function type:
static_assert( std::is_same_v<
function_set_cvref_as_t<void() const, int() &>,
void() & >);
A small example of function_traits usage
A contrived example that type-checks printf
-like member functions that may
or may not be variadic, then forwards a C++ argument pack to the C varargs
(the vargs could be matched and type checked against the format string).
#include <tuple>
#include "function_traits.hpp"
struct Log0 { int log(char const* fmt) const noexcept; };
struct LogV { int log(char const* fmt,...) const & noexcept; };
template <class C, typename F, typename... Vargs>
int logger(F C::* log_mf, Vargs... vargs) noexcept
{
static_assert( std::is_function_v<F> );
static_assert( ltl::function_is_const_v<F> );
static_assert( ltl::function_is_noexcept_v<F> );
static_assert( ltl::function_is_variadic_v<F>
== bool{sizeof...(vargs)} );
using R = ltl::function_return_type_t<F>;
using Ps = ltl::function_arg_types<F,std::tuple>;
using P0 = std::tuple_element_t<0,Ps>;
static_assert( std::is_same_v< R, int> );
static_assert( std::is_same_v< P0, char const*> );
return (C{}.*log_mf)("logger",vargs...);
}
template int logger(decltype(&Log0::log));
template int logger(decltype(&LogV::log),int);
Meson build script provided, e.g. use with ninja backend
meson build
ninja -C build
ninja -C build test
Linux Travis | Windows Appveyor |
---|---|
gcc-8, clang-7 -std=c++17 |
MSVC 15.9.4 /std:c++latest |