Шаблон специализированной функции на основе лямбда-арности - PullRequest
5 голосов
/ 05 июля 2019

Я пытаюсь специализировать шаблонную функцию на основе арма лямбды, которую я передаю ей в качестве аргумента.Вот что я придумала для решения:

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 универсальной лямбды

Однако последнее решение (florestan's) превращает все аргументы в aribtrary_t, поэтому их нужно отбрасывать обратно внутрь каждоголямбда, которую я не нахожу идеальной.В идеале мне хотелось бы напрямую специализировать шаблон higherOrderFun с SFINAE, но для этого я использую вспомогательный класс.Есть ли более прямой путь?Например, применить SFINAE непосредственно к higherOrderFun, не полагаясь на класс helper?Весь смысл в том, что не нужно менять higherOrderFun на higherOrderFun2 и higherOrderFun3, а нужно, чтобы компилятор выводил правильную специализацию из лямбды и заданного аргумента (const T& a).

Я должен отметить, что меня также не волнует тип аргументов функции - только их количество, поэтому я бы изменил decltype(a.someVal()) на auto в моем примере, если бы это было возможно(может быть, есть способ обойти явное определение типов?).

Ответы [ 2 ]

3 голосов
/ 05 июля 2019

Следующий шаблон дает мне количество параметров для лямбды, std::function или простого указателя на функцию.Это, кажется, охватывает все основы.Итак, вы специализируетесь на n_lambda_parameters<T>::n и включаете это в свой шаблон.В зависимости от конкретных случаев использования вам может понадобиться использовать средства, предлагаемые std::remove_reference_t или std::decay_t, чтобы обернуть это.

Протестировано с g ++ 9. Требуется std::void_t из C ++ 17, многопримеров имитации 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;
}
2 голосов
/ 05 июля 2019

Я бы использовал разные перегрузки:

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 из 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);
    }
    // ...
}
...