#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 не обрабатывает необработанные массивы, но это выходит за рамки)