Как определить количество параметров std::function?

У меня следующая проблема. Допустим, вы хотите написать универсальную функцию, которая может принимать лямбда-выражения. Я понимаю, что если параметр имеет тип std::function, то я мог бы использовать не только лямбда-выражения, но также функции и даже указатели на функции. Итак, на первом этапе я сделал следующее:

void print(std::function<void(int, int)> fn) {
  fn(1,2);
}

int main() {

  print([](int i, int j) { std::cout << j <<','<<i<<'\n'; });
  return 0;
}

Теперь проблема в том, что я хочу сделать эту функцию общей, то есть я не хочу, чтобы лямбда-выражение имело только два параметра. Поэтому я попытался изменить сигнатуру функции печати на что-то более общее, например:

template <class function_type>
void print(function_type fn);

Но теперь проблема в том, что функция принимает ЛЮБОЙ объект, и я не согласен с этим. Но главная проблема в том, что я понятия не имею, сколько параметров может принять объект fn.

Таким образом, в некотором смысле я ищу способ времени компиляции, чтобы определить, сколько аргументов имеет fn, и, если возможно, изменить тип fn на std::function. И затем, учитывая, что я знаю количество параметров, которые принимает fn, существует ли общий способ упаковки произвольного количества параметров, которые должны быть переданы в fn? Я даже не знаю, возможно ли это в C++11. Я имею в виду, что, учитывая количество аргументов, есть ли способ упаковать параметры для передачи в fn? Так что если есть два аргумента, то я бы назвал

fn(arg1, arg2);

если их три:

fn(arg1, arg2, arg3);

и так далее.

Спасибо всем за понимание.

аа

3 ответа

Следующие фрагменты могут быть полезны.

Это дает количество аргументов, которые std::function принимает

template <typename Signature>
struct count_args;

template <typename Ret, typename... Args>
struct count_args<std::function<Ret(Args...)>> {
    static constexpr size_t value = sizeof...(Args);
};

Например, следующий код компилируется (clang 3.2, gcc 4.7.2 и icc 13.1.0)

static_assert(count_args<std::function<void()        >>::value == 0, "Ops!");
static_assert(count_args<std::function<void(int)     >>::value == 1, "Ops!");
static_assert(count_args<std::function<void(int, int)>>::value == 2, "Ops!");

Насколько я понимаю, вы хотите вызывать объект функции, передавая правильное количество аргументов, верно? Затем для каждого аргумента нам нужно указать значение, которое можно преобразовать в его тип. Решение с такой общностью очень сложно (или даже невозможно). Поэтому я представлю две альтернативы.

1 Каждый аргумент является значением инициализированного объекта своего типа. (Это то, что предложил ecatmur.)

template <typename Ret, typename... Args>
Ret call(const std::function<Ret(Args...)>& f) {
    return f(Args{}...); // for the intel compiler replace {} with ()
}

2 Задано фиксированное значение, и все аргументы неявно инициализируются из этого значения:

template <typename Ret, typename... Args, typename Val, typename... Vals>
typename std::enable_if<sizeof...(Args) == sizeof...(Vals), Ret>::type
call(const std::function<Ret(Args...)>& f, const Val&, const Vals&... vals) {
    return f(vals...);
}

template <typename Ret, typename... Args, typename Val, typename... Vals>
typename std::enable_if<(sizeof...(Args) > sizeof...(Vals)), Ret>::type
call(const std::function<Ret(Args...)>& f, const Val& val, const Vals&... vals) {
    return call(f, val, val, vals...);
}

Три перегрузки однозначны и могут использоваться, как показано в следующих примерах:

{
    std::function<char()> f = []() -> char {
        std::cout << "f() ";
        return 'A';
    };
    std::cout << call(f)    << std::endl; // calls f()
    std::cout << call(f, 0) << std::endl; // calls f()
}
{
    std::function<char(int)> f = [](int i) -> char {
        std::cout << "f(" << i << ") ";
        return 'B';
    };
    std::cout << call(f)    << std::endl; // calls f(0)
    std::cout << call(f, 1) << std::endl; // calls f(1)
}
{
    std::function<char(int, int)> f = [](int i, int j) -> char {
        std::cout << "f(" << i << "," << j << ") ";
        return 'C';
    };
    std::cout << call(f)    << std::endl; // calls f(0, 0)
    std::cout << call(f, 2) << std::endl; // calls f(2, 2)
}

Чтобы определить сигнатуру вызываемого объекта, вы можете использовать решение из Inferring сигнатуры вызова лямбды или произвольного вызова для "make_function". Затем вы можете упаковать вызываемое в std::functionили создайте тег и используйте вывод параметров:

template<typename T> struct tag {};

template<typename F, typename... Args>
void print_impl(F &&fn, tag<void(Args...)>) {
  fn(Args{}...);
}

template<typename F>
void print(F &&fn) {
  print_impl(std::forward<F>(fn), tag<get_signature<F>>{});
}

Обратите внимание, что для этого используются аргументы, инициализированные значением; если вы хотите что-то более сложное, вы можете построить std::tuple<Args...> и передать его, вызывая его для "распаковки" кортежа, чтобы вызвать соответствующий указатель на функцию.

Да, вы можете упаковать как можно больше параметров fn как вы хотите, используя вариадические шаблоны.

template <class function_type, class... Args>
void print(function_type fn, Args... args)
{
    //Call fn with args
    fn(std::forward<Args>(args...));
}

Чтобы узнать, сколько аргументов в пакете параметров, вы можете использовать sizeof...(args),

Другие вопросы по тегам