【问题标题】:Check a type is a functor including generic lambda检查一个类型是一个函子,包括泛型 lambda
【发布时间】:2017-02-17 09:36:44
【问题描述】:

我可以写一个特征元函数来判断一个类型是否是函子吗? 例如,有大量代码可以使用decltype(&T::operator()) 的 SFINAE 来检查函子,

template<class T>
struct is_functor {
    template<class F>
    static auto test(decltype(&F::operator())) -> std::true_type;
    template<class F>
    static auto test(...) -> std::false_type;
    static constexpr bool value = decltype(test<T>(0))::value;
};

但是,这不适用于泛型 lambda,因为泛型 lambda 的operator() 是一个模板函数。

有一些用于泛型 lambda 版本的有限情况的代码,它们对泛型 lambda 的参数类型进行了一些限制。例如,如果 lambda 表达式包含任何对 int 类型无效的表达式(例如成员访问操作),则此处的答案 (https://stackoverflow.com/a/5117641/2580815) 将不起作用。

我不需要任何通用性。事实上,我只需要知道一个类型可以是一个只接受一个参数的函子。

如何实现我的is_functor

用例:

我正在尝试验证给定参数是否是模板函数的仿函数,也就是说,我想要一些重载的模板函数,例如:

template<class F, class = enable_if_t<is_functor<std::decay_t<F>>::value>>
auto make_func(F &&f) { return std::forward<F>(f); }
template<class F, class = enable_if_t<!is_functor<std::decay_t<F>>::value>>
auto make_func(F &&f) { return [f=std::forward<F>(f)] (auto&&) { return f; }; }

【问题讨论】:

  • 如果您的编译器足够新以支持某些 C++17 功能,您或许可以使用std::is_callable
  • @Someprogrammerdude 它只适用于已知的参数类型。
  • @Someprogrammerdude 不,std::is_callable 完全不同。它需要同时使用仿函数类型F 和参数类型Args... 来解决模棱两可的重载函数。我不在乎论点的模棱两可。我只关心 operator() 的存在。
  • 为什么您需要知道operator() 是否存在并且可以接受一些未知类型的参数
  • @n.m.我添加了一个用例示例。

标签: c++ lambda functor generic-lambda


【解决方案1】:

没有正确的方法可以做到这一点(至少在我们得到静态反射之前)。您能做的最好的事情就是检查一个对象是否可调用,并有一定的信心:

  1. 尝试获取其operator() 地址。如果失败,则该对象可能是不可调用,或者其operator() 可能被重载/模板化。

  2. 尝试使用为常用函数提供接口的虚拟any_type 实例调用对象。这可能有助于您推断出它的数量。

  3. 如果一切都失败了,强制用户以某种方式帮助推导arity或手动指定arity。

您可以解决此问题的一种方法是使用一组deduced_arity 标签:

namespace deduced_arity
{
    template <std::size_t TS>
    struct deducible_t : std::integral_constant<std::size_t, TS>
    {
    };

    struct undeducible_t
    {
    };

    constexpr undeducible_t undeducible{};
    constexpr deducible_t<1> unary{};
}

您还需要某种function_traits 实现,它会静态地告诉您函数对象的确切数量。这可以是found in Boost

那你还需要an implementation of any_type

之后,您可以使用以下类型特征来检查 函数对象 可能是否被重载,使用 检测习语

template <typename T>
using is_not_overloaded_impl = decltype(&T::operator());

template <typename T>
using is_not_overloaded =
    std::experimental::is_detected<is_not_overloaded_impl, T>;

然后您可以使用if constexpr(...)(或任何其他编译时分支机制) 做出一个很好的猜测 - 示例:

if constexpr(is_not_overloaded<T>{})
{
    // use `function_traits` here
}
else if constexpr(std::is_callable<T(any_type)>{})
{
    return deduced_arity::unary;
}
else if constexpr(/* user manually marked arity */)
{
    /* deal with user-defined deduction helpers */
}
else
{
    return deduced_arity::undeducible;
}

【讨论】:

  • 所以,基本上,没有办法处理通用 lambda,例如 [] (auto &&v) { return v.a_member; },对吗?
  • @xylosper:不是真的。您要么必须将a_member 添加到any_type,要么不使用auto
【解决方案2】:

重用来自my answer to a twin question 的大部分代码(关于查找函数的arity):

template <class, std::size_t N, class = std::make_index_sequence<N>, class = void_t<>>
struct CanCall : std::false_type { };

template <class F, std::size_t N, std::size_t... Idx>
struct CanCall<
    F, N,
    std::index_sequence<Idx...>,
    void_t<decltype(std::declval<F>()((Idx, std::declval<Any const&&>())...))>
> : std::true_type { };

CanCall&lt;F, N&gt; 将返回 F 是否可以使用任意类型的 N 参数调用。 Any 帮助器类型具有模板化的隐式转换运算符,允许它变形为任何所需的参数类型。

然后,针对您的特定用例(一元函子):

template <class F>
struct IsUnaryFunctor : CanCall<F, 1u> { };

See it live on Coliru

【讨论】:

  • 您的回答不适用于auto l3 = [](auto&amp;&amp; v) { return v.a_member; };
  • @xylosper mmh,我明白了。我认为这种方法无法解决这个问题,因为推断返回类型需要实例化 lambda 的主体......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-07
  • 1970-01-01
  • 2015-12-12
  • 1970-01-01
相关资源
最近更新 更多