У меня есть следующие функции, где processArray
— это общая функция, используемая для обработки массива. В качестве параметра ему передается функция, чтобы можно было также обработать каждый элемент.
template<typename TItem>
size_t processArray(const TItem* value, const size_t length, size_t (*processItem)(const TItem)) {
size_t output = length;
for (auto i = 0; i < length; ++i) {
output += processItem(value[i]);
}
return output;
}
size_t processInt(const int value) {
return value;
}
size_t processString(const std::string& value) {
return value.length();
}
Затем я хочу обработать массив int
и std::string
, но сталкиваюсь с ошибкой компиляции (поскольку (*processItem)(const TItem)
не принимает параметр ссылочного типа).
// Int
const int intArray[2] { 1, 2 };
const auto intArrayResult = processArray(intArray, 2, processInt); // OK
// String
const std::string strArray[2] { "123", "456" };
const auto strArrayResult = processArray(strArray, 2, processString); // Error: No matching function for call to 'processArray'
Однако, если я изменю объявление параметра processItem
на size_t (*processItem)(const TItem&)
- заставлю его принять ссылочный тип, то я не смогу пройти processInt
.
const int intArray[2] { 1, 2 };
const auto intArrayResult = processArray(intArray, 2, processInt); // Error: No matching function for call to 'processArray'
// String
const std::string strArray[2] { "123", "456" };
const auto strArrayResult = processArray(strArray, 2, processString); // OK
Итак, кто-нибудь знает, как processArray
можно написать так, чтобы функция объявлялась только один раз и могла принимать processItem
, которая может обрабатывать как значения, так и ссылочные типы? Или нет способа это осуществить?
// NG function declaration is repeated
template<typename TItem>
size_t processArray(const TItem* value, const size_t length, size_t (*processItem)(const TItem));
template<typename TItem>
size_t processArray(const TItem* value, const size_t length, size_t (*processItem)(const TItem&));
Просто используйте шаблоны до конца и заставьте функцию также вводить шаблон:
template<typename TItem, typename Func>
size_t processArray(const TItem* value, const size_t length, Func func) {
std::size_t output = length;
for (std::size_t i = 0; i < length; ++i) {
output += func(value[i]);
}
return output;
}
И вы можете увидеть, как это работает на этом живом примере.
Единственное предостережение в отношении этого подхода: он потерпит неудачу, если ваша функция перегружена. Если это так, вам нужно будет обернуть его в лямбда-выражение, например
const auto intArrayResult = processArray(intArray, 2, [](const auto& val) { return overloaded_function(val); });
Если вы имеете дело исключительно с простыми необработанными массивами, вы также можете настроить функцию для получения массива по ссылке, что позволяет вам также не передавать размер. Это также позволяет вам использовать цикл for на основе диапазона. Это будет выглядеть как
template<typename TItem, std::size_t N, typename Func>
size_t processArray(const TItem (&values)[N], Func func) {
std::size_t output = N;
for (const auto& val : values) {
output += func(val);
}
return output;
}
Для предложения, которое вы сделали во втором фрагменте, было бы достаточно всего template<typename Array, typename Func> size_t processArray(const Array &values, Func func)....
. Кроме того, использование std::accumulate
делает его красивым.
@JeJo У вас есть пример использования накопления здесь? Я не понимаю, как это применить, поскольку мы не суммируем массив, а суммируем возвращаемое значение каждого обработанного элемента. Я думаю, вы могли бы пойти по пути range
и сделать transform_view
, а затем накопить это, но не уверен, действительно ли это то, что нужно ОП. Без накопления результатов for_each
кажется более подходящим алгоритмом.
Не нужно долго думать, обычный std::accumulate
подойдет: gcc.godbolt.org/z/485Kzvxvs
@JeJo Мех, я бы предпочел использовать цикл for на основе диапазона. Легче читать и понимать, ИМХО.
Предположим, вы не можете изменить processArray
и processString
вы не можете использовать лямбду:
std::vector<std::string> x(20);
std::cout << processArray(x.data(),x.size(),+[](std::string s){ return processString(s); });
Если вы можете их изменить, сделайте тип вызываемого аргументом шаблона, как предложено здесь.
У меня была похожая проблема: мне нужно было передать шаблонное значение в функцию шаблона, как показано ниже, а затем возникла ошибка компиляции:
error: no matching function for call to 'processArray(const int&, const int [2], int, <unresolved overloaded function type>)'
template<typename Stream, typename TItem, typename Func>
size_t processArray(Stream &stream, const TItem* value, const size_t length, Func func) {
size_t output = length;
for (auto i = 0; i < length; ++i) {
output += func(stream, value[i]);
}
return output;
}
template<typename Stream>
size_t processString(Stream &stream, const std::string& value) {
return value.length();
}
template<typename Stream>
size_t processInt(Stream &stream, const int value) {
return value;
}
Решением является явное указание шаблона параметра Stream
:
int main()
{
MyStream stream(4096);
// Int
const int intArray[2] { 1, 2 };
const auto intArrayResult = processArray(stream, intArray, 2, processInt<MyStream>);
// String
const std::string strArray[2] { "123", "456" };
const auto strArrayResult = processArray(stream, strArray, 2, processString<MyStream>);
}
Живая демонстрация: https://godbolt.org/z/G6KofobrY
Было бы нормально, если бы у вас было два параметра шаблона:
template<typename TItem, typename F> size_t processArray(const TItem* value, const size_t length, F processItem)
?