Сейчас я изучаю C++ и пробую некоторые функции шаблонов. Я пытаюсь создать общий шаблон, который получает функцию F
от U
до V
, std::array
типа U
, а затем возвращает (через NRVO) массив типа V
.
Это то, что я придумал при первом проходе. Моему неопытному глазу это кажется разумным, но компилятору это не нравится:
template <typename F, typename U, typename V, std::size_t N> std::array<V, N> map(F mapper, const std::array<U, N> &elements)
{
std::array<V, N> newArray{};
for (std::size_t i = 0; i < N; i++) {
newArray[i] = mapper(elements[i]);
}
return newArray;
}
// ...
int main()
{
std::array<int, 10> ints1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto doubles1 = map([](int x) -> double { return x*2.5; }, ints1);
}
Я использую clang 16.0.6, и он говорит мне:
functional.cpp:87:19: error: no matching function for call to 'map'
auto doubles1 = map([](int x) -> double { return x*2.5; }, ints1);
^~~~~~~~~~~~
functional.cpp:22:79: note: candidate template ignored: couldn't infer template argument 'V'
template <typename F, typename U, typename V, std::size_t N> std::array<V, N> map(F mapper, std::array<U, N> &elements)
^
1 error generated.
На мой взгляд, мне кажется, что я могу что-то сделать, чтобы намекнуть компилятору, что лямбда возвращает двойное значение, и, следовательно, auto
следует вывести как std::array<double, N>
. Есть ли что-то простое, что мне не хватает? Спасибо.
Я конечно ожидал, что код скомпилируется, но что-то не совсем так. Я все еще новичок в жаргоне C++, поэтому мне трудно понять, что именно нужно Google, чтобы решить эту проблему. Одним из возможных решений, которое я придумал, было использование выходного параметра:
template <typename F, typename U, typename V, std::size_t N> void map_outParam(F mapper, std::array<U, N> &elements, std::array<V, N> &out)
{
for (std::size_t i = 0; i < N; i++) {
out[i] = mapper(elements[i]);
}
}
Это прекрасно компилируется и работает так, как ожидалось. Однако здесь не так чисто, как хотелось бы.
Мне было бы любопытно посмотреть, о чем вы говорите с std::invoke_result
. Основываясь на другом ответе, мне нужно немного прочитать о выводе типа шаблона. В моем коде U
— это int
, а V
— это double
. Но это было просто для примера. Я пытался понять общий случай любой комбинации U
/V
.
Вы были близки, но порядок аргументов шаблона сбивал с толку:
#include <cstddef>
#include <array>
#include <iostream>
template <typename V, std::size_t N, typename F, typename U>
std::array<V, N> map(
F mapper,
std::array<U, N>& elements
) {
std::array<V, N> newArray{};
for (std::size_t i = 0; i < N; i++) {
newArray[i] = mapper(elements[i]);
}
return newArray;
}
// ...
int main()
{
std::array<int, 10> ints1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto doubles1 = map<double>(
[](int x) -> double {
return x*2.5;
},
ints1
);
for (auto it = doubles1.begin(); it != doubles1.end(); ++it) {
std::cout<<*it<<'\n';
}
}
Разница в том, что существует понятие неявных аргументов шаблона, при котором компилятор определяет типы шаблонов. То, как вы их изначально заказали, не учитывало, что V
невозможно вывести, поэтому нет функции сопоставления.
Таким образом, приведенный выше пример корректирует порядок и явно определяет, что такое V
.
Великолепно! Я знал, что должен быть простой способ сделать это. Мне нужно будет еще немного почитать о выводе типов шаблонов. Спасибо :)
По сути, вы можете просто использовать тип результата вашей функции обратного вызова, чтобы определить тип массива результатов.
#include <type_traits>
#include <array>
template <typename F, typename U, std::size_t N> auto map(F mapper, std::array<U, N> &elements)
{
std::array<std::invoke_result_t<F, U>, N> newArray{};
for (std::size_t i = 0; i < N; i++) {
newArray[i] = mapper(elements[i]);
}
return newArray;
}
// ...
int main()
{
std::array<int, 10> ints1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto doubles1 = map([](int x) -> double { return x*2.5; }, ints1);
}
Это тоже очень круто. Надо будет об этом прочитать std::invoke_result_t
. Кажется, это устраняет необходимость явно указывать тип возвращаемого значения сопоставителя. Спасибо!
Также обратите внимание, что ваш цикл делает то же самое, что и std::transform
. Этот ответ уже опередил меня в решении типовых признаков, но в любом случае вот моя реализация. Явный тип возвращаемого значения для лямбды не требуется. godbolt.org/z/EexfPE79j
Начинаем соревнование по гольфу на языке C++! :)
Да, я просто вносил «минимальные изменения» в существующий код, а не переписывал его.
Сопоставленные элементы не могут быть созданы по умолчанию.
Вы можете сделать это проще с помощью std::apply
и std::array
CTAD.
template <typename F, typename U, std::size_t N>
auto map(F mapper, std::array<U, N>& elements)
{
return std::apply([&](auto&... args) {
return std::array{mapper(args)...};
}, elements);
}
Если вы хотите разрешить другие функции сопоставления, вы должны иметь возможность использовать
auto
в качестве параметра результата и определять тип возвращаемого значения вашего преобразователя с помощьюstd::invoke_result
. Посмотрим, смогу ли я заставить это работать...