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.
Problem A
The primary template uses
The specialization is defined as:
The mistake is providing
Cas the second template argument tostd::enable_if. You only ever use specializations offunction_type_infowhere the second parameter isvoid, notC.To fix this, write:
Problem B
The call operator of lambda expressions is
constby default, so you need another specialization to cover that:Then, define 46 more partial specializations to cover all combinations of
const,volatile,noexcept, ref-qualifiers, and variadic arguments.Here is a compact version:
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.