【问题标题】:Determine if callable object has template arguments确定可调用对象是否具有模板参数
【发布时间】:2017-10-15 21:26:18
【问题描述】:

是否可以确定可调用对象(函数、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 不是函数。这是一个函数模板。 f3 也不是函数,而是闭包对象。
  • 这里可能存在一些 X-Y 问题。愿意解释一下您这样做的动机吗?
  • @krOoze 对不起,我可能应该更广泛地解释我的问题,但我现在有我需要的东西来解决它。谢谢:)。

标签: c++ templates c++14


【解决方案1】:

不,我不相信这是可能的。

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

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

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

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

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

【讨论】:

  • 我明白了,但是如何确定表达式 (&amp;F::operator()) 是否有效而不会导致编译错误?
【解决方案2】:

理论上编译器可以知道。

但是没有。 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 并将函数包装在测试模板中来解决其中的一些问题。再一次,它会很丑陋,而且通常不能正常工作。

【讨论】:

  • 感谢您的回答。你能用一些代码解释一下,我想我会明白的吗?
  • 当然,给我几分钟。公平警告:它很丑。
  • 如果 f1 在其声明中使用 SFINAE 以使重载决议不会考虑 f1&lt;int&gt;,那么在进行评估之前,仅编写 f1&lt;int&gt; 会立即出现编译器错误function_param_t&lt; f1&lt;int&gt;, 0 &gt;.
  • function_traits 不处理 lambda 类型或 std::function 特化以外的其他可调用类类型。需要一些额外的工作来检查 F::operator() 的有效性和形式来支持这一点。
  • @aschepler 有效点。好吧,正如我在 A 开头指出的那样,模板参数必须是一个类型——不可能,AFAIK,围绕它。如果您能够实例化 auto 模板,则应该在 lambda 上工作(也毫无意义,因为您知道该参数是一个模板)——尽管尚未测试。
猜你喜欢
  • 2018-05-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-29
  • 2020-02-15
  • 1970-01-01
  • 2011-01-30
相关资源
最近更新 更多