Перебор векторов в пакете параметров

#c #function #templates #vector #variadic-templates

#c #функция #шаблоны #вектор #переменные-шаблоны

Вопрос:

У меня есть функция, которая принимает в пакете параметров векторы, эти векторы будут иметь одинаковую длину, и я хочу вызвать функцию со значениями обоих этих векторов по каждому индексу.

Например, я передаю функцию, которая выводит два целых числа, которые я передаю. Я бы ввел в функцию два вектора целых чисел, и она вызовет функцию, которую я передаю для каждого из индексов, код может помочь объяснить, что я хочу сделать

 #include <iostream>
#include <thread>
#include <functional>
#include <vector>

template<
    typename RETURN,
    typename ... INPUTS
>
std::vector<RETURN> thread_transform(std::function<RETURN(INPUTS ...)> function, std::vector<INPUTS>amp; ... inputs)
{
    std::cout << __PRETTY_FUNCTION__ << std::endl;
    /* Code to loop over the vectors in 'inputs' and call 'function' with */
}

int func(int a, int b)
{
    std::cout << a << ' ' << b << std::endl;
}

int main(int argc, char** argv)
{
    std::vector<int> a;
    std::vector<int> b;
    std::vector<int> ret = thread_transform(std::function<int(int, int)>(func), a, b);
    return 0;
}
 

Ответ №1:

Возможно, что-то в этом роде:

 template<typename RETURN, typename ... INPUTS>
std::vector<RETURN> thread_transform(
    std::function<RETURN(INPUTS ...)> function,
    std::vector<INPUTS>amp; ... inputs)
{
    std::vector<RETURN> ret;
    auto size = std::min({inputs.size()...});
    for (int i = 0; i < size;   i) {
        ret.push_back(function(inputs[i]...));
    }
    return ret;
}
 

Довольно просто. ДЕМОНСТРАЦИЯ

Комментарии:

1. Ах, это очень умно, я бы сам этого не понял. Спасибо!

2. Только один вопрос, мне любопытно, как вы выполнили функцию std::min. Это просто передача списка инициализаторов в функцию std::min, и вызывается перегрузка, которая работает с коллекциями? Когда я смотрю на определение функции std::min, кажется, что оно может сравнивать только два значения.

3. std::min имеет перегрузку, которая принимает список инициализаторов. Перегрузка (3) здесь

4. Рассмотрите возможность определения размера результирующего вектора (при условии, что значения являются инициализируемыми по значению или конструируемыми по умолчанию): std::vector<RETURN> ret(size); ;

5. Лучше всего убедиться, что ни один входной вектор не испортит ситуацию: auto size = std::min({std::size_t(-1), inputs.size()...}); кроме того, ret.reserve(size); это не помешает. Наконец, i тоже должно быть типа std::size_t .

Ответ №2:

Я бы предложил упростить сигнатуру функции, а не жестко кодировать для vectors or std::function .

Поскольку вы уже определяете шаблон функции, вы также можете принять любые диапазоны и любые вызываемые:

В проводнике компилятора

 template <typename F, typename... Rs>
auto zip(F f, Rs constamp;... args) {
    using R = std::decay_t<
        std::invoke_result_t<F, typename Rs::value_type...>
    >;
    auto const n = std::min({args.size()...});
    std::vector<R> r(n);
    for (size_t i = 0; i<n;   i)
        r[i] = f(args[i]...);
    return r;
}
 

Фактически, когда вы обобщаете, вы получите RangeV3 zip_with :

 template <typename F, typename... Rs>
auto my_zip(Famp;amp; f, Rs constamp;... args) {
    return r::to_vector(v::zip_with(f, args...));
}
 

Я бы сказал, что вы можете обойтись без to_vector по умолчанию.

В проводнике компилятора

 #include <fmt/ranges.h>
#include <functional>
#include <iostream>
#include <map>
#include <range/v3/all.hpp>
#include <vector>

namespace r = ::ranges;
namespace v = r::views;
using namespace std::string_literals;

template <typename F, typename... Rs>
auto zip(F f, Rs constamp;... args) {
    return v::zip_with(std::move(f), args...);
}

template <typename F, typename... Rs>
auto zip_vec(F constamp; f, Rs constamp;... args) {
    return r::to_vector(zip(f, args...));
}

auto foo(int x, std::string_view s) {
    std::string r(s.size() * x, '');
    while (x--) {
        std::copy(s.begin(), s.end(), r.begin()   x * s.size());
    }
    return r;
}

int main() {
    std::vector<int> a{ 1, 3 }, b{ 2, -2 };

    auto mul = std::multiplies<>{};
    fmt::print("{} x {} -> {}n", a, b, zip(mul, a, b));

    auto ret = zip_vec(std::plus<>{}, a, b);
    fmt::print("ret as a vector: {}n", ret);
    static_assert(std::is_same_v<decltype(ret), std::vector<int>>);

    std::map<std::string, int> m { {"one"s, 1}, {"three"s, 3} };
    fmt::print("But also using a non-vectors: {}nor {}n",
    zip(foo, a, m | v::keys),
    zip(mul, a, m | v::values));
}
 

С принтами

 {1, 3} x {2, -2} -> {2, -6}
ret as a vector: {3, 1}
But also using a non-vectors: {"one", "threethreethree"}
or {1, 9}
 

Комментарии:

1. Это мой голос, хотя он подавляется собственными массивами. Нет необходимости отклонять их на самом деле.

2. @Deduplicator WorksForMe™ . Использование только стандартной библиотеки работает с небольшими изменениями, полагающимися на range_value_t .

3. @Deduplicator и если у вас еще нет ничего из этих «диапазонов», я бы немного перефразировал это: godbolt.org/z/Ecn49K (однако обратите внимание, что libfmt не обрабатывает необработанные массивы, но это выходит за рамки)