I am playing around with SFINAE and trying, as an exercise, to get information about "function-like" objects (functions, function pointers, lambdas, and anything overloading the parenthesis operator). I've stripped my code down to the following:
#include <iostream>
#include <functional>
#include <string>
#include <type_traits>
template<typename Callable, typename = void>
struct function_type_info;
template<typename R, typename G, typename... T>
struct function_type_info<R(G::*)(T...)> {
using return_type = R;
};
template<typename C>
struct function_type_info<C, typename std::enable_if<!std::is_member_function_pointer<C>::value, C>::type> {
using return_type = typename function_type_info<decltype(&C::operator())>::return_type;
};
struct Callback {
int operator()(double, std::string) {
return 0;
}
};
int main(int argc, char** argv) {
auto b = [](double, std::string) -> int { return 0; };
auto c = Callback{};
std::cout << std::boolalpha;
std::cout << std::is_same<function_type_info<decltype(&Callback::operator())>::return_type, int>::value << std::endl;
std::cout << std::is_same<function_type_info<decltype(b)>::return_type, int>::value << std::endl;
std::cout << std::is_same<function_type_info<decltype(c)>::return_type, int>::value << std::endl;
return 0;
}
The compiler gives me the following errors:
<source>:32:64: error: incomplete type 'function_type_info<main(int, char**)::<lambda(double, std::string)> >' used in nested name specifier
32 | std::cout << std::is_same<function_type_info<decltype(b)>::return_type, int>::value << std::endl;
| ^~~~~~~~~~~
<source>:32:80: error: template argument 1 is invalid
32 | std::cout << std::is_same<function_type_info<decltype(b)>::return_type, int>::value << std::endl;
| ^
<source>:33:64: error: incomplete type 'function_type_info<Callback>' used in nested name specifier
33 | std::cout << std::is_same<function_type_info<decltype(c)>::return_type, int>::value << std::endl;
| ^~~~~~~~~~~
<source>:33:80: error: template argument 1 is invalid
33 | std::cout << std::is_same<function_type_info<decltype(c)>::return_type, int>::value << std::endl;
I cannot figure out why I'm getting this error. With lambdas and Callback objects, the second specialization of function_type_info
should be accepted: std::is_member_function_pointer<C>::value
is false, so !std::is_member_function_pointer<C>::value
is true, so std::enable_if<!std::is_member_function_pointer<C>::value, C>::type
should evaluate to C
and the template deduction should succeed.
If I change std::is_member_function_pointer<C>::value, C>::type
into std::is_member_function_pointer<C>::value, void>::type
then is works for Callback
, but not for lambdas. Again, I'm at a loss as to why this change makes it work for Callback
in the first place, and why, if it works for Callbacks
, it doesn't for lambdas. The error for lambdas is the following:
<source>: In instantiation of 'struct function_type_info<main(int, char**)::<lambda(double, std::string)> >':
<source>:32:62: required from here
<source>:16:11: error: invalid use of incomplete type 'struct function_type_info<int (main(int, char**)::<lambda(double, std::string)>::*)(double, std::__cxx11::basic_string<char>) const, void>'
16 | using return_type = typename function_type_info<decltype(&C::operator())>::return_type;
| ^~~~~~~~~~~
<source>:7:8: note: declaration of 'struct function_type_info<int (main(int, char**)::<lambda(double, std::string)>::*)(double, std::__cxx11::basic_string<char>) const, void>'
7 | struct function_type_info;
| ^~~~~~~~~~~~~~~~~~
<source>: In function 'int main(int, char**)':
<source>:32:80: error: template argument 1 is invalid
32 | std::cout << std::is_same<function_type_info<decltype(b)>::return_type, int>::value << std::endl;
It's as if I was not allowed to refer to the lambda's parenthesis operator...
Note: I know I can get return type information through other ways (std::result_of
/std::invoke_result
), but as I said, I'm doing this as an exercise.
You have two separate problems in your code. Here is a working version.
The primary template uses
template<typename Callable, typename = void>
struct function_type_info;
The specialization is defined as:
template<typename C>
struct function_type_info<C, typename std::enable_if<!std::is_member_function_pointer<C>::value, C>::type> {
using return_type = typename function_type_info<decltype(&C::operator())>::return_type;
};
The mistake is providing C
as the second template argument to std::enable_if
.
You only ever use specializations of function_type_info
where the second parameter is void
, not C
.
To fix this, write:
std::enable_if<!std::is_member_function_pointer<C>::value, void>
// or simply
std::enable_if<!std::is_member_function_pointer<C>::value>
The call operator of lambda expressions is const
by default, so you need another specialization to cover that:
template<typename R, typename G, typename... T>
struct function_type_info<R(G::*)(T...) const, void> {
using return_type = R;
};
Then, define 46 more partial specializations to cover all combinations of const
, volatile
, noexcept
, ref-qualifiers, and variadic arguments.
Here is a compact version:
template<typename>struct pointer_to_member_function_traits;
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A...)>{constexpr static bool is_variadic=false;using class_type=S;using reference_type=S&;using function_type=R(A...);using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A...)noexcept>{constexpr static bool is_variadic=false;using class_type=S;using reference_type=S&;using function_type=R(A...)noexcept;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A...)const>{constexpr static bool is_variadic=false;using class_type=S;using reference_type=const S&;using function_type=R(A...)const;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A...)const noexcept>{constexpr static bool is_variadic=false;using class_type=S;using reference_type=const S&;using function_type=R(A...)const noexcept;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A...)volatile>{constexpr static bool is_variadic=false;using class_type=S;using reference_type=volatile S&;using function_type=R(A...)volatile;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A...)volatile noexcept>{constexpr static bool is_variadic=false;using class_type=S;using reference_type=volatile S&;using function_type=R(A...)volatile noexcept;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A...)const volatile>{constexpr static bool is_variadic=false;using class_type=S;using reference_type=const volatile S&;using function_type=R(A...)const volatile;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A...)const volatile noexcept>{constexpr static bool is_variadic=false;using class_type=S;using reference_type=const volatile S&;using function_type=R(A...)const volatile noexcept;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A...)&>{constexpr static bool is_variadic=false;using class_type=S;using reference_type=S&;using function_type=R(A...)&;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A...)&noexcept>{constexpr static bool is_variadic=false;using class_type=S;using reference_type=S&;using function_type=R(A...)&noexcept;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A...)const&>{constexpr static bool is_variadic=false;using class_type=S;using reference_type=const S&;using function_type=R(A...)const&;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A...)const&noexcept>{constexpr static bool is_variadic=false;using class_type=S;using reference_type=const S&;using function_type=R(A...)const&noexcept;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A...)volatile&>{constexpr static bool is_variadic=false;using class_type=S;using reference_type=volatile S&;using function_type=R(A...)volatile&;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A...)volatile&noexcept>{constexpr static bool is_variadic=false;using class_type=S;using reference_type=volatile S&;using function_type=R(A...)volatile&noexcept;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A...)const volatile&>{constexpr static bool is_variadic=false;using class_type=S;using reference_type=const volatile S&;using function_type=R(A...)const volatile&;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A...)const volatile&noexcept>{constexpr static bool is_variadic=false;using class_type=S;using reference_type=const volatile S&;using function_type=R(A...)const volatile&noexcept;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A...)&&>{constexpr static bool is_variadic=false;using class_type=S;using reference_type=S&&;using function_type=R(A...)&&;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A...)&&noexcept>{constexpr static bool is_variadic=false;using class_type=S;using reference_type=S&&;using function_type=R(A...)&&noexcept;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A...)const&&>{constexpr static bool is_variadic=false;using class_type=S;using reference_type=const S&&;using function_type=R(A...)const&&;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A...)const&&noexcept>{constexpr static bool is_variadic=false;using class_type=S;using reference_type=const S&&;using function_type=R(A...)const&&noexcept;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A...)volatile&&>{constexpr static bool is_variadic=false;using class_type=S;using reference_type=volatile S&&;using function_type=R(A...)volatile&&;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A...)volatile&&noexcept>{constexpr static bool is_variadic=false;using class_type=S;using reference_type=volatile S&&;using function_type=R(A...)volatile&&noexcept;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A...)const volatile&&>{constexpr static bool is_variadic=false;using class_type=S;using reference_type=const volatile S&&;using function_type=R(A...)const volatile&&;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A...)const volatile&&noexcept>{constexpr static bool is_variadic=false;using class_type=S;using reference_type=const volatile S&&;using function_type=R(A...)const volatile&&noexcept;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A......)>{constexpr static bool is_variadic=true;using class_type=S;using reference_type=S&;using function_type=R(A......);using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A......)noexcept>{constexpr static bool is_variadic=true;using class_type=S;using reference_type=S&;using function_type=R(A......)noexcept;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A......)const>{constexpr static bool is_variadic=true;using class_type=S;using reference_type=const S&;using function_type=R(A......)const;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A......)const noexcept>{constexpr static bool is_variadic=true;using class_type=S;using reference_type=const S&;using function_type=R(A......)const noexcept;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A......)volatile>{constexpr static bool is_variadic=true;using class_type=S;using reference_type=volatile S&;using function_type=R(A......)volatile;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A......)volatile noexcept>{constexpr static bool is_variadic=true;using class_type=S;using reference_type=volatile S&;using function_type=R(A......)volatile noexcept;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A......)const volatile>{constexpr static bool is_variadic=true;using class_type=S;using reference_type=const volatile S&;using function_type=R(A......)const volatile;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A......)const volatile noexcept>{constexpr static bool is_variadic=true;using class_type=S;using reference_type=const volatile S&;using function_type=R(A......)const volatile noexcept;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A......)&>{constexpr static bool is_variadic=true;using class_type=S;using reference_type=S&;using function_type=R(A......)&;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A......)&noexcept>{constexpr static bool is_variadic=true;using class_type=S;using reference_type=S&;using function_type=R(A......)&noexcept;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A......)const&>{constexpr static bool is_variadic=true;using class_type=S;using reference_type=const S&;using function_type=R(A......)const&;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A......)const&noexcept>{constexpr static bool is_variadic=true;using class_type=S;using reference_type=const S&;using function_type=R(A......)const&noexcept;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A......)volatile&>{constexpr static bool is_variadic=true;using class_type=S;using reference_type=volatile S&;using function_type=R(A......)volatile&;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A......)volatile&noexcept>{constexpr static bool is_variadic=true;using class_type=S;using reference_type=volatile S&;using function_type=R(A......)volatile&noexcept;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A......)const volatile&>{constexpr static bool is_variadic=true;using class_type=S;using reference_type=const volatile S&;using function_type=R(A......)const volatile&;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A......)const volatile&noexcept>{constexpr static bool is_variadic=true;using class_type=S;using reference_type=const volatile S&;using function_type=R(A......)const volatile&noexcept;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A......)&&>{constexpr static bool is_variadic=true;using class_type=S;using reference_type=S&&;using function_type=R(A......)&&;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A......)&&noexcept>{constexpr static bool is_variadic=true;using class_type=S;using reference_type=S&&;using function_type=R(A......)&&noexcept;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A......)const&&>{constexpr static bool is_variadic=true;using class_type=S;using reference_type=const S&&;using function_type=R(A......)const&&;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A......)const&&noexcept>{constexpr static bool is_variadic=true;using class_type=S;using reference_type=const S&&;using function_type=R(A......)const&&noexcept;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A......)volatile&&>{constexpr static bool is_variadic=true;using class_type=S;using reference_type=volatile S&&;using function_type=R(A......)volatile&&;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A......)volatile&&noexcept>{constexpr static bool is_variadic=true;using class_type=S;using reference_type=volatile S&&;using function_type=R(A......)volatile&&noexcept;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A......)const volatile&&>{constexpr static bool is_variadic=true;using class_type=S;using reference_type=const volatile S&&;using function_type=R(A......)const volatile&&;using return_type=R;using argument_types=std::tuple<A...>;};
template<typename R,typename S,typename...A>struct pointer_to_member_function_traits<R(S::*)(A......)const volatile&&noexcept>{constexpr static bool is_variadic=true;using class_type=S;using reference_type=const volatile S&&;using function_type=R(A......)const volatile&&noexcept;using return_type=R;using argument_types=std::tuple<A...>;};
Or use this code from wreien/lazy-query, which is slightly less insane because it uses macros to generate these 48 combinations.
This is obviously half-serious.
It just demonstrates why it's a bit silly to get the return type directly from the member function pointer type instead of using std::invoke_result
.