Как вывести тип возврата лямбды? - PullRequest
5 голосов
/ 10 ноября 2019

Я хочу имитировать метод Руби map() в C ++. Я изо всех сил пытаюсь выяснить тип возвращаемого значения автоматически:

#include <vector>
#include <string>
#include <algorithm>
#include <iostream>

typedef std::string T2;

template<class T1,
//  class T2, // gives "couldn't deduce template parameter 'T2'"
    class UnaryPredicate>
std::vector<T2> map(std::vector<T1> in, UnaryPredicate pred)
{
    std::vector<T2> res(in.size());
    std::transform(in.begin(), in.end(), res.begin(), pred);
    return res;
}

int main()
{
    std::vector<int> v1({1,2,3});
    auto v2(map(v1, [](auto el) { return "'"+std::to_string(el+1)+"'"; }));
    std::cout << v2[0] << "," << v2[1] << "," << v2[2] << std::endl;
}

Таким образом, он компилируется, но T2 фиксируется на string. Если я использую другое определение T2, компилятор жалуется couldn't deduce template parameter 'T2'. Я также пытался использовать std::declval, но, вероятно, не правильно - я не смог решить проблему.

Ответы [ 2 ]

10 голосов
/ 10 ноября 2019

Использование decltype + std::decay_t:

template <class T, class UnaryPredicate>
auto map(const std::vector<T>& in, UnaryPredicate pred)
{
    using result_t = std::decay_t<decltype(pred(in[0]))>;

    std::vector<result_t> res;
    res.reserve(in.size());
    std::transform(in.begin(), in.end(), std::back_inserter(res), pred);
    return res;
}

Пример использования:

std::vector v1{1, 2, 3};
auto v2 = map(v1, [](int el) { return "'" + std::to_string(el + 1) + "'"; });
std::cout << v2[0] << ", " << v2[1] << ", " << v2[2] << '\n';

( живая демонстрация )

Пожалуйстатакже обратите внимание на следующие изменения, которые я сделал:

  1. Я изменил in, чтобы принимать по константной ссылке, а не по значению. Это позволяет избежать ненужных копий.

  2. Я использовал reserve + back_inserter вместо инициализации значения + присваивания.

  3. Я использовал auto как тип возвращаемого значения. Это позволяет вычитать тип возврата. Вектор res гарантированно не будет скопирован. Он также имеет право на получение копии.

  4. Вы можете инициализировать список из braced-init-list напрямую, поэтому удалите скобки, окружающие braced-init-list .

  5. std::endl не должен использоваться, когда достаточно \n. std::endl вызывает сброс буфера, а \n - нет. Ненужная промывка может привести к снижению производительности. См. std::endl против \n.

4 голосов
/ 10 ноября 2019

Для упрощения использования auto в качестве типа возврата и value_type вектора можно указать с помощью declval - вызов UnaryPredicate для T1:

template<class T1,
    class UnaryPredicate>
auto map(std::vector<T1> in, UnaryPredicate pred)
{
    std::vector< decltype(std::declval<UnaryPredicate>()(T1{})) > res(in.size());
    std::transform(in.begin(), in.end(), res.begin(), pred);
    return res;
}

Демо

...