基于lambda arity的专用功能模板

时间:2019-07-05 01:20:28

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

我正在尝试根据作为参数传递给它的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的)将所有参数转换为aribtrary_t,因此必须将它们强制转换回每个lambda内,我认为并不理想。理想情况下,我希望直接使用SFINAE对模板化的higherOrderFun进行专用化,但是由于它是我使用的帮助器类来实现这一点。还有更简单的方法吗?例如将SFINAE直接应用于higherOrderFun而不依赖于helper类吗?这样做的全部目的是不必将higherOrderFun更改为higherOrderFun2higherOrderFun3,而是让编译器根据lambda和给定的参数({{1} }。

我应该提到的是,我也不在乎函数参数的类型-只是在乎它们的数量,因此如果我将示例中的const T& a更改为decltype(a.someVal()),可能(也许有一种规避显式定义类型的方法?)。

2 个答案:

答案 0 :(得分:3)

以下模板为我提供了lambda,std::function或普通函数指针的参数数量。这似乎涵盖了所有基础知识。因此,您专门研究n_lambda_parameters<T>::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;
}

答案 1 :(得分: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);
    }
    // ...
}