确定可调用对象是否具有模板参数

Determine if callable object has template arguments

本文关键字:参数 是否 调用 对象      更新时间:2023-10-16

是否可以确定可调用对象(函数、lambda表达式、函子等)的第一个参数是否依赖于模板参数?例如:(使用假设类型特征)

#include <iostream>
template <typename T>
void f1(T x) {}
void f2(int x) {}
int main() {
auto f3 = [](auto x) {};
std::cout << std::boolalpha
<< is_first_arg_generic<decltype(f1)>::value << std::endl
<< is_first_arg_generic<decltype(f2)>::value << std::endl;
<< is_first_arg_generic<decltype(f3)>::value << std::endl;
}

输出:

true
false
true

不,我认为这是不可能的。

函数模板f1很难使用,因为在类型或表达式中几乎所有可能使用f1的情况都必须立即将其转换为特定类型,只选择模板的一个专用化,否则会立即导致编译器错误。没有办法只传递模板名称,以便在SFINAE上下文中使用,因为模板模板参数只用于类模板和别名模板,而不是函数模板。

实际上,对于一组重载的非模板函数的名称,您也会遇到完全相同的问题。

但有一件事更接近于帮助:将所需的函数模板或重载函数封装在泛型lambda中。现在,它至少可以用作模板参数。问题就归结为计算出你的f3

如果给定的类型以某种方式已知或假定为闭包类型,那么很容易检测它是否是泛型lambda的类型:表达式&F::operator()对非泛型lambda有效,对泛型lambda无效。此外,您可以检测是否可以使用参数类型的特定列表来调用任何可调用类型,和/或是否可以通过类型推导来专门化给定可调用类的operator(),以生成参数类型的指定列表

但我认为这已经是我们所能做的了。实际上有无限多的可能的参数/参数类型列表。如果你能以某种方式将你关心的检查可能性限制在一个有限的列表中,你可以全部尝试。(或者,如果你可以选择一组不依赖于任意标识符的可计数可能性,你可以尽可能多地尝试编译器的最大实例化次数。)但这并不能真正解决问题。如果多个参数类型作为第一个参数有效,那么它仍然可以是一个非模板函数,其第一个参数类型可以从多个类型隐式转换。或者,即使您可以确定只有一个第一个参数类型有效,或者只有一个函数参数列表有效,您仍然可以有一个函数模板,其第一个参数是依赖类型,该依赖类型仅根据通过其他方式推导出的可能模板参数计算为一个特定类型。

理论上编译器可以知道。

但不是。f1是一个模板,不是一个函数,甚至不是一个类型,所以不能将它传递给任何模板参数列表。

尽管可以确定函数类型的第一参数的类型。您可以尝试实例化模板两次,如果第一个参数的类型在两个模板实例之间不同,那么它就是一个模板化参数。当然,它通常不会适用于每种类型的模板(即,在一些模板中,对于两个不同的模板参数,参数可能是相同的类型)。

我做这个是为了我的用途(可能有更好的变体,也许Boost中有什么?):

#include <functional>
template< typename F >
struct function_traits;
template< typename Result, typename... Params >
struct function_traits< Result( Params... ) >{
static const size_t paramCount = sizeof...( Params );
using result = Result;
template< size_t i >
using param = typename std::tuple_element<  i, std::tuple< Params... >  >::type;
};
template< typename Result, typename... Params >
struct function_traits< Result(*)( Params... ) >
: public function_traits< Result( Params... ) > {
};
template< typename Result, typename... Params >
struct function_traits<  std::function< Result( Params... ) >  >
: public function_traits< Result( Params... ) >{
};
// shortcuts to help avoid the weird "typename" and "template" disambiguators
template< typename T >
using function_result_t = typename function_traits<T>::result;
template< typename T, size_t i >
using function_param_t = typename function_traits<T>::template param<i>; // lol, that's evil syntax

所以在这种情况下,我会像这样使用它:

using first_param_type_1st_try = function_param_t< f1<int>, 0 >;
using first_param_type_2nd_try = function_param_t< f1<unsigned>, 0 >;
bool is_first_param_probably_templated
= !std::is_same_v<first_param_type_1st_try, first_param_type_2nd_try>;

不过,这是毫无意义的,因为您知道非模板函数有非模板第一个参数。您必须将任何模板名称实例化为类型名称。

您可以使用SFINAE并将函数封装在测试模板中来解决其中的一些问题。再说一遍,这将是丑陋的,而且通常不会起作用。