Edit 2:
New edit: it looks like C 20 has a new ranges library, which does what I want from the functional point of view. How would something similar be done on C 17 or earlier? Also, would the Kotlin syntactic sugar be possible? Mainly the person example:
val adam = Person("Adam").apply {
age = 20 // same as this.age = 20 or adam.age = 20
city = "London"
}
Edit 1:
I don't know if my question was that clear, so I'll give an example using Rust's map.
This is how a map is done in rust:
let newVector = myVector.iter().map(|x|, x * 2)
This is how it is done in C
std::string s("hello");
std::vector<std::size_t> ordinals;
std::transform(s.begin(), s.end(), std::back_inserter(ordinals),
[](unsigned char c) -> std::size_t { return c; });
This is a lot more verbose.
I'd like to add some syntactic sugar to do something like this instead:
std::string s("hello");
auto ordinals = my_ns::map(s,[](unsigned char c) -> std::size_t { return c; });
The implementation of my_ns::map could be something like this (I'm sure this will not work, it's just to show how it could be done)
template<typename T, U>
map(T t, std::function<U> f)
{
std::vector<U> ordinals;
std::transform(t.begin(), t.end(), std::back_inserter(ordinals),
[](U c) -> f(c));
return ordinals;
}
In this case ordinals doesn't need to be of type std::vector<std::size_t>, it could be of a type map which has a conversion for std::vector<T>. The reason for a type map is to be able to chain functions like reduce with map.
Original Question
Note: this is for a personal project only, I do not intend to use it in production or when working with C most of the time.
I have been using a lot of Kotlin lately and I'm dabbling a little with Rust and I love their higher level features. That got me wondering, can I create some syntactic sugar to emulate some of these features?
Some of the stuff I'd be trying to emulate(just an example)
Rust map:
let newVector = myVector.iter().map(|x|, x * 2)
Kotlin let
val adam = Person("Adam").apply {
age = 20 // same as this.age = 20 or adam.age = 20
city = "London"
}
val str = "Hello"
str.let {
println("The string's length is ${it.length}")
}
I do not plan on using the exact same syntax, but I'd like to know if it would be possible to do something like:
int[] arr = {1, 2, 3};
auto d_arr = map(arr, [](int x){return x*2}) // maybe return a map type to be able to use .reduce, etc
The first kotlin let example I wouldn't know how to do, but the second could be solved using lambdas as well.
I'm thinking of using new types and create conversions to std::vector and arrays, kind of how C#'s LINQ works.
Any tips on how to do this are appreciated.
Ps: I know about std::transform and other functions(I'd probably use them when implementing this), but I'd like a simpler API than the ones offered (and I also think that it would be a nice personal project).
CodePudding user response:
Your example can be changed to working C code fairly easily:
template <typename T, typename Fun> auto map(T t, Fun f) {
std::vector<typename T::value_type> ordinals;
std::transform(t.begin(), t.end(), std::back_inserter(ordinals), f);
return ordinals;
}
This is neither very idiomatic nor optimized for performance, but it works. (Pre-resize the vector instead of using std::back_inserter and pass t by reference for a big performance boost.)
Edit: You can add reduce as follows:
template <typename T, typename R, typename Fun> auto reduce(T t, R init, Fun f) {
return std::transform(t.begin(), t.end(), init, f);
}
And then combine them: reduce(map(x, [](auto a) {...}), 0, [](int& a, auto b) {...})
The obvious caveat: This is going to be really slow. These functions are fun to learn template concepts, but in practice, ranges or old-style std::reduce/... will be much, much faster.
CodePudding user response:
I'd like to know if it would be possible to do something like:
int[] arr = {1, 2, 3}; auto d_arr = map(arr, [](int x){return x*2})
"map" in this sense is called "transform" in C standard library. A working example:
auto double_ = [](auto x){ return x*2; }; // you may also use a normal function
auto doubled_view = std::views::all(arr)
| std::views::transform(double_);
to be able to chain functions like reduce with map.
You can reduce ranges. Unfortunately, the C 20 standard ranges don't come with a reduce function, so one has to use either another implementation of ranges, or use the iterator based functions instead. Example of latter:
auto sum = std::reduce(
std::execution::unseq, // optional execution policy
doubled_view.begin(),
doubled_view.end(),
0
// you can use a functor to do something other than add
);
conversions to std::vector
You can also create containers from ranges:
std::vector doubled_vector(
doubled_view.begin(), doubled_view.end());
Note that creating containers for intermediate stages of range views can have unnecessary overhead.
