Info
-
Did you know that C++17 made exception specifications be part of the type system?
Example
int main() {
void (*f1)();
void (*f2)() noexcept;
f1 = f2; // okay
f2 = f1; // ill-formed since C++17
}
Puzzle
- Can you implement
function_args_t
type trait which takes an invocable, respectnoexcept
and returns its arguments?
struct args{};
struct va_args {};
template<class T>
using function_args_t = /*TODO*/
int f1();
int f2() noexcept;
constexpr auto f3() noexcept -> void;
constexpr auto f4(...) noexcept -> void;
constexpr auto f5(int, ...) noexcept -> void;
constexpr auto f6(int...) noexcept -> void;
auto l1 = [] {};
auto l2 = [](int...) noexcept {};
auto l3 = [](auto...) noexcept {};
auto l4 = [](auto......) noexcept {};
constexpr auto l5 = [](auto..., auto......) constexpr noexcept {};
constexpr auto l6 = []<class... Ts>(Ts..., auto......) constexpr noexcept {};
constexpr auto l7 = []<auto...>(...) constexpr noexcept {};
struct f {
constexpr auto operator()(int, double, float) noexcept(false) -> void;
};
static_assert(std::is_same_v<std::tuple<>, function_args_t<decltype(&f1)>>);
static_assert(std::is_same_v<std::tuple<>, function_args_t<decltype(&f2)>>);
static_assert(std::is_same_v<std::tuple<>, function_args_t<decltype(&f3)>>);
static_assert(std::is_same_v<std::tuple<va_args>, function_args_t<decltype(&f4)>>);
static_assert(std::is_same_v<std::tuple<int, va_args>, function_args_t<decltype(&f5)>>);
static_assert(std::is_same_v<std::tuple<int, va_args>, function_args_t<decltype(&f6)>>);
static_assert(std::is_same_v<std::tuple<>, function_args_t<decltype(l1)>>);
static_assert(std::is_same_v<std::tuple<int, va_args>, function_args_t<decltype(l2)>>);
static_assert(std::is_same_v<std::tuple<args>, function_args_t<decltype(l3)>>);
static_assert(std::is_same_v<std::tuple<args, va_args>, function_args_t<decltype(l4)>>);
static_assert(std::is_same_v<std::tuple<args, va_args>, function_args_t<decltype(l5)>>);
static_assert(std::is_same_v<std::tuple<args, va_args>, function_args_t<decltype(l6)>>);
static_assert(std::is_same_v<std::tuple<va_args>, function_args_t<decltype(l7)>>);
static_assert(std::is_same_v<std::tuple<int, double, float>, function_args_t<f>>);
Solutions
// forward decl for later specialization
template <class T> struct function_args;
// specialization for regular function
template <class R, class... Ts, auto NE>
struct function_args<R(Ts...) noexcept(NE)> {
using arg_list_t = std::tuple<Ts...>;
};
// specialization for varargs function
template <class R, class... Ts, auto NE>
struct function_args<R(Ts..., ...) noexcept(NE)> {
using arg_list_t = std::tuple<Ts..., va_args>;
};
// combinatorial explosion ahead
// specializations for const and non-const member functions
// with and without varargs
template <class C, class R, class... Ts, auto NE>
struct function_args<R(C::*)(Ts...) const noexcept(NE)> : function_args<R(Ts...) noexcept(NE)> {};
template <class C, class R, class... Ts, auto NE>
struct function_args<R(C::*)(Ts..., ...) const noexcept(NE)> : function_args<R(Ts..., ...) noexcept(NE)> {};
template <class C, class R, class... Ts, auto NE>
struct function_args<R(C::*)(Ts...) noexcept(NE)> : function_args<R(Ts...) noexcept(NE)> {};
// technically not needed for test
//template <class C, class R, class... Ts, auto NE>
//struct function_args<R(C::*)(Ts..., ...) noexcept(NE)> : function_args<R(Ts..., ...) noexcept(NE)> {};
// function overloads for getting the right thing out
// raw function pointers
template <class T> requires std::is_pointer_v<T>
constexpr auto fn_args() -> function_args<std::remove_pointer_t<T>>;
// non-generic lambdas
template <class T> requires requires { &T::operator(); }
constexpr auto fn_args() -> function_args<decltype(&T::operator())>;
// generic lambdas with class template parms
template <typename T> requires requires { &T::template operator()<args>; }
constexpr auto fn_args() -> function_args<decltype(&T::template operator()<args>)>;
// generic lambdas with NTTPs
template <typename T> requires requires { &T::template operator()<1>; }
constexpr auto fn_args() -> function_args<decltype(&T::template operator()<1>)>;
// one alias to rule them all
template <class T>
using function_args_t = decltype(fn_args<T>())::arg_list_t;
template<class T>
struct function_traits : function_traits<decltype(&T::operator())> {};
template<class T> requires requires { T::template operator()<args>; }
struct function_traits<T> : function_traits<decltype(&T::template operator()<args>)> {};
template<class T> requires requires { T::template operator()<0>; }
struct function_traits<T> : function_traits<decltype(&T::template operator()<0>)> {};
template<class R, class... TArgs, auto Noexcept>
struct function_traits<R(*)(TArgs...) noexcept(Noexcept)> { using args = std::tuple<TArgs...>; };
template<class R, class B, class... TArgs>
struct function_traits<R(B::*)(TArgs...)> { using args = std::tuple<TArgs...>; };
template<class R, class... TArgs, auto Noexcept>
struct function_traits<R(*)(TArgs......) noexcept(Noexcept)> { using args = std::tuple<TArgs..., va_args>; };
template<class R, class B, class... TArgs, auto Noexcept>
struct function_traits<R(B::*)(TArgs...) const noexcept(Noexcept)> { using args = std::tuple<TArgs...>; };
template<class R, class B, class... TArgs, auto Noexcept>
struct function_traits<R(B::*)(TArgs......) const noexcept(Noexcept)> { using args = std::tuple<TArgs..., va_args>; };
template<class T>
using function_args_t = typename function_traits<T>::args;
namespace detail {
constexpr int some_nttp = 0;
}
template<typename T> struct func_info_t;
template<typename Ret, typename... Args, auto no_except_val>
struct func_info_t<Ret(*)(Args...) noexcept(no_except_val)> {
using args_t = std::tuple<Args...>;
};
template<typename Ret, typename... Args, auto no_except_val>
struct func_info_t<Ret(*)(Args..., ...) noexcept(no_except_val)> {
using args_t = std::tuple<Args..., va_args>;
};
template<typename Class, typename Ret, typename... Args, auto no_except_val>
struct func_info_t<Ret(Class::*)(Args...) noexcept(no_except_val)> {
using args_t = std::tuple<Args...>;
};
template<typename Class, typename Ret, typename... Args, auto no_except_val>
struct func_info_t<Ret(Class::*)(Args...) const noexcept(no_except_val)> {
using args_t = std::tuple<Args...>;
};
template<typename Class, typename Ret, typename... Args, auto no_except_val>
struct func_info_t<Ret(Class::*)(Args..., ...) const noexcept(no_except_val)> {
using args_t = std::tuple<Args..., va_args>;
};
template<typename T> requires std::is_pointer_v<T>
constexpr auto function_pointer_maker() -> T;
template<typename T> requires requires { &T::operator(); }
constexpr auto function_pointer_maker() -> decltype(&T::operator());
template<typename T> requires requires { &T::template operator()<args>; }
constexpr auto function_pointer_maker() -> decltype(&T::template operator()<args>);
template<typename T> requires requires { &T::template operator()<detail::some_nttp>; }
constexpr auto function_pointer_maker() -> decltype(&T::template operator()<detail::some_nttp>);