【问题标题】:Specializing function template based on lambda arity基于 lambda arity 的专业化函数模板
【发布时间】:2019-11-15 15:25:14
【问题描述】:

我正在尝试根据我作为参数传递给它的 lambda 的数量来专门化一个模板化函数。这是我想出的解决方案:

template<typename Function, bool>
struct helper;

template<typename Function>
struct helper<Function, false>
{
    auto operator()(Function&& func)
    {
        std::cout << "Called 2 argument version.\n";
        return func(1, 2);
    }
};

template<typename Function>
struct helper<Function, true>
{
    auto operator()(Function&& func)
    {
        std::cout << "Called 3 argument version.\n";
        return func(1, 2, 3);
    }
};

template<typename T>
struct B
{
    T a;
    const T someVal() const { return a; }
};

template<typename Function, typename T>
auto higherOrderFun(Function&& func, const T& a)
{
    return helper<Function, std::is_invocable<Function, decltype(a.someVal()), decltype(a.someVal()), decltype(a.someVal())>::value>{}(std::forward<Function>(func));
}


int main()
{
    B<int> b;
    std::cout << higherOrderFun([](auto x, auto y) {return x+y; }, b) << "\n";
    std::cout << higherOrderFun([](auto x, auto y, auto z) {return x + y+z; }, b) << "\n";
    return 0;
}

有没有办法以更优雅的方式实现这一目标?我看过这个:Arity of a generic lambda

但是,最新的解决方案(florestan's)将所有参数转换为aribtrary_t,因此必须将它们转换回每个 lambda 内部,我认为这并不理想。理想情况下,我希望使用 SFINAE 直接专门化模板化的 higherOrderFun,但因为它是我使用辅助类来实现的。有没有更直接的方法?例如直接将 SFINAE 应用到 higherOrderFun 而不依赖于 helper 类?这样做的重点是不必将higherOrderFun 更改为higherOrderFun2higherOrderFun3,而是让编译器从lambda 和给定参数(const T&amp; a)中推断出正确的特化。

我应该提到,我也不关心函数参数的类型 - 只关心它们的数量,所以如果可能的话,我会在我的示例中将 decltype(a.someVal()) 更改为 auto(也许有一种规避显式定义类型的方法?)。

【问题讨论】:

  • 如果func既可以接受两个参数又可以接受三个参数呢?
  • "aribtrary_t,因此必须将它们转换回每个 lambda"。不,它仅在未评估的上下文中使用(因此,如果有,请不要实例化通用 lambda)。
  • @Jarod42 我尝试在 msvc2017 中将他的 arity_v 用于 SFINAE,它需要在每个 lambda 中进行强制转换,因为当我使用 arity_v 时它显然是用 arbitrary_t 实例化了它。不过,当我回来时,我可以尝试创建一个最小的示例。
  • 如何,确实它的特征并没有像我们预期的那样工作Demo
  • @Jarod42 是的,这正是我的意思。谢谢你举个例子,我在原帖里已经提到了。

标签: c++ templates lambda c++17 arity


【解决方案1】:

以下模板为我提供了 lambda、std::function 或普通函数指针的参数数量。这似乎涵盖了所有基础知识。因此,您专注于 n_lambda_parameters&lt;T&gt;::n,并将其插入您的模板。根据您的具体用例,您可能需要使用std::remove_reference_tstd::decay_t 提供的工具来包装它。

使用 g++ 9 测试。需要 C++17 中的 std::void_t,在其他地方可以找到大量模拟 std::void_t pre C++17 的示例...

#include <functional>

// Plain function pointer.

template<typename T> struct n_func_parameters;

template<typename T, typename ...Args>
struct n_func_parameters<T(Args...)> {

    static constexpr size_t n=sizeof...(Args);
};

// Helper wrapper to tease out lambda operator()'s type.

// Tease out closure's operator()...

template<typename T, typename> struct n_extract_callable_parameters;

// ... Non-mutable closure
template<typename T, typename ret, typename ...Args>
struct n_extract_callable_parameters<T, ret (T::*)(Args...) const> {

    static constexpr size_t n=sizeof...(Args);
};

// ... Mutable closure
template<typename T, typename ret, typename ...Args>
struct n_extract_callable_parameters<T, ret (T::*)(Args...)> {

    static constexpr size_t n=sizeof...(Args);
};

// Handle closures, SFINAE fallback to plain function pointers.

template<typename T, typename=void> struct n_lambda_parameters
    : n_func_parameters<T> {};

template<typename T>
struct n_lambda_parameters<T, std::void_t<decltype(&T::operator())>>
    : n_extract_callable_parameters<T, decltype(&T::operator())> {};


#include <iostream>

void foo(int, char, double=0)
{
}

int main()
{
    auto closure=
        [](int x, int y)
    // With or without mutable, here.
        {
        };

    std::cout << n_lambda_parameters<decltype(closure)>::n
          << std::endl; // Prints 2.

    std::cout << n_lambda_parameters<decltype(foo)>::n
          << std::endl; // Prints 3.

    std::cout << n_lambda_parameters<std::function<void (int)>>::n
          << std::endl; // Prints 1.
    return 0;
}

【讨论】:

  • 请注意,OP 使用通用 lambda,因此您的解决方案将不起作用(至于任何重载的 operator() BTW)。
  • 不幸的是,将您的代码直接复制粘贴到 msvc 中,或者 Godbolt 的最新 msvc 版本 (19.21) 无法编译。错误的第一部分内容为:'n_extract_callable_parameters&lt;T,_Ret (__thiscall std::_Func_class&lt;_Ret,int&gt;::* )(int) const&gt;': base class undefined 我不确定您是否在做违反标准的事情,或者是 msvc 不能充分支持标准。但不管怎样,它在主要编译器上都失败了。
【解决方案2】:

我会使用不同的重载:

template<typename Function>
auto higherOrderFun(Function&& func)
-> decltype(std::forward<Function>(func)(1, 2, 3))
{
    return std::forward<Function>(func)(1, 2, 3);
}

template<typename Function>
auto higherOrderFun(Function&& func)
-> decltype(std::forward<Function>(func)(1, 2))
{
    return std::forward<Function>(func)(1, 2);
}

可能以重载优先级为

 struct low_priority {};
 struct high_priority : low_priority{};

template<typename Function>
auto higherOrderFunImpl(Function&& func, low_priority)
-> decltype(std::forward<Function>(func)(1, 2))
{
    return std::forward<Function>(func)(1, 2);
}

template<typename Function>
auto higherOrderFunImpl(Function&& func, high_priority)
-> decltype(std::forward<Function>(func)(1, 2))
{
    return std::forward<Function>(func)(1, 2);
}

template<typename Function>
auto higherOrderFun(Function&& func)
-> decltype(higherOrderFun(std::forward<Function>(func), high_priority{}))
{
    return higherOrderFun(std::forward<Function>(func), high_priority{});
}

如果你想使用arity traits from florestan,它可能会导致:

template<typename F>
decltype(auto) higherOrderFun(F&& func)
{
    if constexpr (arity_v<std::decay_t<F>, MaxArity> == 3)
    {
        return std::forward<F>(func)(1, 2, 3);
    }
    else if constexpr (arity_v<std::decay_t<F>, MaxArity> == 2)
    {
        return std::forward<F>(func)(1, 2);
    }
    // ...
}

【讨论】:

  • 如果我决定不直接返回函数的结果,有什么办法可以做到这一点?另请注意,1,2,3 只是占位符 - 通常,参数可能来自 a.someVal() 或类似的东西。 (我应该事后澄清这一点)。我喜欢优先的想法。
  • 是的,您可以将1, 2, 3 替换为a.someval()
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-18
  • 2011-10-29
  • 2013-12-27
  • 1970-01-01
  • 2020-07-25
相关资源
最近更新 更多