Một recursive_transform cho std :: vector với nhiều kiểu trả về
Đây là câu hỏi tiếp theo cho Một hàm recursive_transform cho nhiều kiểu lặp có thể lồng vào nhau Với std :: biến thể Triển khai trong C ++ và Một hàm get_from_variant trong C ++ . Cảm ơn G. Sliepen và Quuxplusone đã cung cấp các gợi ý đánh giá chi tiết. Tuy nhiên, phiên bản tồn tại của recursive_transformhàm giả định kiểu trả về luôn giống kiểu đầu vào. Nói cách khác, nó hoạt động tốt với hàm lambda như [](double x)->double { return x + 1; }(kiểu của cả đầu vào và đầu ra là double) hoặc [](int x)->int { return x + 1; }(kiểu của cả đầu vào và đầu ra là int). Trong bước tiếp theo, tôi muốn tập trung vào trường hợp kiểu trả về khác với kiểu nhập. Ví dụ,[](int x)->std::string { return std::to_string(x); }. Bởi vì kiểu trả về gốc của recursive_transformđược chỉ định T, nó không thể xử lý trường hợp kiểu của đầu ra được xử lý từ hàm lambda fkhác với T. Hãy thay đổi loại Tthành autonhư dưới đây. autoCú pháp này được sử dụng ở đây làm cho kiểu dẫn xuất trở nên thích nghi.
template<class T, class F>
auto recursive_transform(const T& input, const F& f) {
return f(input);
}
Một phần khác của điều này recursive_transformlà cấu trúc đệ quy và nó phức tạp hơn ở trên. Vùng chứa đã được chỉ định ở std::vectorđây trước.
template<class T, class F> requires is_iterable<T>
auto recursive_transform(const T& input, const F& f) {
typedef typename std::iterator_traits<typename T::iterator>::value_type
value_type;
std::vector<decltype(recursive_transform(std::declval<value_type&&>(), f))> output(input.size());
std::transform(input.begin(), input.end(), output.begin(),
[f](auto& element)
{
return recursive_transform(element, f);
}
);
return output;
}
Việc kiểm tra chức năng mẫu trên recursive_transform.
std::vector<int> test_vector = {
1, 2, 3
};
auto recursive_transform_result = recursive_transform(
test_vector,
[](int x)->std::string { return std::to_string(x); }); // For testing
std::cout << "string: " + recursive_transform_result.at(0) << std::endl; // recursive_transform_result.at(0) is a std::string
std::vector<decltype(test_vector)> test_vector2 = {
test_vector, test_vector, test_vector
};
auto recursive_transform_result2 = recursive_transform(
test_vector2,
[](int x)->std::string { return std::to_string(x); }); // For testing
std::cout << "string: " + recursive_transform_result2.at(0).at(0) << std::endl; // recursive_transform_result.at(0).at(0) is also a std::string
Một liên kết Godbolt ở đây.
Tất cả các đề xuất đều được chào đón.
Câu hỏi nào là tiếp theo?
Một hàm recursive_transform cho nhiều loại Lặp lại lồng nhau Có thể lặp lại với std :: biến thể Triển khai trong C ++ và
Một hàm get_from_variant trong C ++
Những thay đổi nào đã được thực hiện trong mã kể từ câu hỏi trước?
Trong phiên bản trước của
recursive_transformhàm, nó hoạt động tốt khi kiểu trả về giống với kiểu đầu vào. Ý tưởng chính trong câu hỏi này là cố gắng triển khai một phiên bản mở rộng mà kiểu trả về khác với kiểu đầu vào.Tại sao một đánh giá mới lại được yêu cầu?
Hàm phiên bản trước
recursive_transformgiả định kiểu trả về luôn giống kiểu đầu vào. Tôi đang cố gắng tập trung vào trường hợp kiểu trả về khác với kiểu đầu vào để làm cho hàm chung chung hơn. Tuy nhiên, tôi biết rằng tôi làm cho các thuật toán của mình trở nên chuyên biệt hơnstd::vectortrong phiên bản mã này. Tôi không biết làm thế nào để triển khai mộtrecursive_transformhàm chung chung hơn trong cả loại đầu ra khác nhau và loại vùng chứa khác nhau một cách đơn giản và thông minh. Nếu có bất kỳ đề xuất hoặc ý tưởng có thể về điều này, xin vui lòng cho tôi biết.
Trả lời
Tôi e rằng điều này đang đạt đến giới hạn hiểu biết của tôi về các mẫu trong C ++, nhưng dù sao tôi cũng sẽ cố gắng trả lời nó tốt nhất có thể:
Sử dụng std::back_inserter()để điền vào các vectơ
Thay vì xây dựng một vectơ có kích thước nhất định, chỉ cần khai báo một vectơ trống, nhưng dành đủ dung lượng và sau đó sử dụng std::back_inserter()để lấp đầy nó:
std::vector<decltype(recursive_transform(std::declval<value_type&&>(), f))> output;
output.reserve(input.size());
std::transform(input.begin(), input.end(), std::back_inserter(output),
[f](auto& element)
{
return recursive_transform(element, f);
}
);
Tuy nhiên, nếu bạn không sử dụng std::vectornhưng một loại vùng chứa khác reserve()và / hoặc std::back_inserter()có thể không phù hợp.
Xác định loại vùng chứa
Vì vậy, lý tưởng nhất là chúng ta không muốn khai báo a std::vector, mà là kiểu vùng chứa bên ngoài T. Bạn có thể sử dụng các thông số của mẫu mẫu để giải cấu trúc các kiểu mẫu:
template<template<class> class Container, class ValueType, class Function>
requires is_iterable<Container<ValueType>>
auto recursive_transform(const Container<ValueType> &input, const Function &f)
{
// You want to be able to write this:
using TransformedValueType = decltype(recursive_transform(*input.begin(), f));
Container<TransformedValueType> output;
...
}
Thật không may, điều đó không hoạt động, ít nhất là không với Clang, vì một std::vectorthực sự có hai tham số mẫu và các vùng chứa khác có thể có nhiều hơn hoặc ít hơn các tham số mẫu. Vì vậy, giải pháp cho vấn đề đó là khai báo ValueTypedưới dạng gói tham số mẫu:
template<template<class...> class Container, class Function, class Ts...>
requires is_iterable<Container<Ts...>>
auto recursive_transform(const Container<Ts...> &input, const Function &f)
{
using TransformedValueType = decltype(recursive_transform(*input.begin(), f));
Container<TransformedValueType> output;
...
}
Mặc dù tất nhiên điều này không chuyển tiếp tham số mẫu thứ hai. Kết hợp mọi thứ cho đến nay với nhau:
template<typename T>
concept is_iterable = requires(T x)
{
*std::begin(x);
std::end(x);
};
template<class T, class Function>
auto recursive_transform(const T &input, const Function &f)
{
return f(input);
}
template<template<class...> class Container, class Function, class... Ts>
requires is_iterable<Container<Ts...>>
auto recursive_transform(const Container<Ts...> &input, const Function &f)
{
using TransformedValueType = decltype(recursive_transform(*input.begin(), f));
Container<TransformedValueType> output;
std::transform(std::begin(input), std::end(input), std::back_inserter(output),
[&](auto &element)
{
return recursive_transform(element, f);
}
);
return output;
}