Каков идиоматический способ создания шаблона функции преобразования U в V в C++?

Сейчас я изучаю 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]);
  }
}

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

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

Christian Stieber 29.04.2024 03:21

Мне было бы любопытно посмотреть, о чем вы говорите с std::invoke_result. Основываясь на другом ответе, мне нужно немного прочитать о выводе типа шаблона. В моем коде U — это int, а V — это double. Но это было просто для примера. Я пытался понять общий случай любой комбинации U/V.

lucas 29.04.2024 03:24
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
2
134
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Вы были близки, но порядок аргументов шаблона сбивал с толку:

#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.

Великолепно! Я знал, что должен быть простой способ сделать это. Мне нужно будет еще немного почитать о выводе типов шаблонов. Спасибо :)

lucas 29.04.2024 03:22
Ответ принят как подходящий

По сути, вы можете просто использовать тип результата вашей функции обратного вызова, чтобы определить тип массива результатов.

#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. Кажется, это устраняет необходимость явно указывать тип возвращаемого значения сопоставителя. Спасибо!

lucas 29.04.2024 03:32

Также обратите внимание, что ваш цикл делает то же самое, что и std::transform. Этот ответ уже опередил меня в решении типовых признаков, но в любом случае вот моя реализация. Явный тип возвращаемого значения для лямбды не требуется. godbolt.org/z/EexfPE79j

paddy 29.04.2024 03:39

Начинаем соревнование по гольфу на языке C++! :)

lucas 29.04.2024 03:43

Да, я просто вносил «минимальные изменения» в существующий код, а не переписывал его.

Christian Stieber 29.04.2024 03:43

Сопоставленные элементы не могут быть созданы по умолчанию.

Вы можете сделать это проще с помощью 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);
}

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