我正在尝试 SFINAE,并尝试获取有关“类函数”对象(函数、函数指针、lambda 以及任何重载括号运算符的内容)的信息,作为练习。我已将代码精简为以下内容:
#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;
}
编译器给我以下错误:
<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;
我不明白为什么会出现此错误。对于 lambda 和 Callback 对象,应接受
function_type_info
的第二个特化:std::is_member_function_pointer<C>::value
为 false,因此 !std::is_member_function_pointer<C>::value
为 true,因此 std::enable_if<!std::is_member_function_pointer<C>::value, C>::type
应计算为 C
并且模板推导应成功。
如果我将
std::is_member_function_pointer<C>::value, C>::type
更改为 std::is_member_function_pointer<C>::value, void>::type
那么适用于 Callback
,但不适用于 lambda。再次,我不明白为什么这个改变首先使它适用于 Callback
,以及为什么如果它适用于 Callbacks
,它不适用于 lambda。 lambda 的错误如下:
<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;
就好像我不被允许引用 lambda 的括号运算符...
注意:我知道我可以通过其他方式获取返回类型信息(
std::result_of
/std::invoke_result
),但正如我所说,我这样做是为了练习。
您的代码中有两个独立的问题。 这是一个工作版本。
主要模板使用
template<typename Callable, typename = void>
struct function_type_info;
专业定义为:
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;
};
错误在于提供
C
作为 std::enable_if
的第二个模板参数。
您只使用 function_type_info
的特化,其中第二个参数是 void
,而不是 C
。
要解决此问题,请写入:
std::enable_if<!std::is_member_function_pointer<C>::value, void>
// or simply
std::enable_if<!std::is_member_function_pointer<C>::value>
lambda 表达式的调用运算符默认为
const
,因此您需要另一个专门化来涵盖它:
template<typename R, typename G, typename... T>
struct function_type_info<R(G::*)(T...) const, void> {
using return_type = R;
};
然后,再定义 46 个部分特化,以涵盖
const
、volatile
、noexcept
、引用限定符和可变参数的所有组合。
这是一个精简版本:
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...>;};
或者使用 来自 wreien/lazy-query 的代码,这稍微不那么疯狂,因为它使用宏来生成这 48 种组合。
这显然是半认真的。 它只是演示了为什么直接从成员函数指针类型获取返回类型而不是使用
std::invoke_result
有点愚蠢。