I’ve got one type of collection and I want to apply a function to each member of the collection, thereby producing a new collection.
Surely there’s a standard pattern for this?
In JavaScript, it’s called map:
function getOldValues()
{
return ["a", "b", "c", "d"];
}
var newValues = getOldValues().map(v => v.charCodeAt(0));
// result: [97, 98, 99, 100]
In C#, it’s Select.
string[] GetOldValues() => new[] { "a", "b", "c", "d" };
var newValues = GetOldValues().Select(v => (int)v[0]).ToArray();
// result: int[] { 97, 98, 99, 100 };
In C++, it’s, um, this clumsy std::transform.
std::vector<std::string> GetOldValues()
{
return { "a", "b", "c", "d" };
}
auto oldValues = GetOldValues();
std::vector<int> newValues;
newValues.reserve(oldValues.size());
std::transform(oldValues.begin(), oldValues.end(),
std::back_inserter(newValues),
[](auto&& v) { return v[0]; });
It’s clumsy because you need to give a name to the thing being transformed, because you need to call both begin and end on it. But giving it a name extends its lifetime, so you end up carrying this oldValues vector around for no reason.¹
It’s clumsy because you have to construct an empty newValues and then fill it in.
Would be nice if there were some helper function like
template<typename T, typename U, typename TLambda>
T transform_to(U&& u, TLambda&& lambda)
{
T result;
if constexpr (has_size_v<U> && has_reserve_v<T>)
{
result.reserve(u.size());
}
std::transform(u.begin(), u.end(), std::back_inserter(result),
std::forward<TLambda>(lambda));
return result;
}
auto newValues = std::transform_to<std::vector<int>>(
GetOldValues(), [](auto&& v) { return v[0]; });
Maybe one exists and I’m missing it? Help me out here.
¹ You can avoid extending the lifetime beyond the transform by pushing it into a lambda:
auto newValues = [&]()
{
auto oldValues = GetOldValues();
std::vector<int> newValues;
newValues.reserve(oldValues.size());
std::transform(oldValues.begin(), oldValues.end(),
std::back_inserter(newValues),
[](auto&& v) { return v[0]; });
return newValues;
}();
but that’s basically just taking the transform_to function and inlining it as a lambda.
I could write the transforming iterator but if G++ ever gets inner functions it would become mostly obsolete.
In classes that don’t use LINQ at all and if both the source and target are array, I would prefer to use Array.ConvertAll() instead.
That’s just personal preference, and I haven’t measured to see if there is any performance benefit to do so.
Last time I used C++ was some 15 years ago and I was barely proficient with it back then. But I have spent the time using various other languages professionally. A few years ago I thought of checking out C++ again because I found the language fascinating.
But I didn't get very far in because I started feeling rather disillusioned about it. It seems to me that it's bogged down... not even by compatibility, but rather outdated ideas and paradigms. For example, the whole string and collection approach in the standard library is like they're trying to emulate pointer arithmetic,...
Maybe the Web(kit) Template Framework or the Mozilla Framework Based on Templates would suit you? (If only because you like their initialisms…)
If the element type being transformed from is implicitly convertible to the element type being transformed to (as is the case with 'char' -> 'int', but not 'std::string' -> 'int'), then you don't need std::transform(), you can construct the destination vector from the source vector directly:
auto oldValues = GetOldValues();
std::vector<int> newValues(oldValues.begin(), oldValues.end());
You do still have the lifetime issue with oldValues, but you can reduce its scope to address that, if you don't want to use a separate function:
std::vector<int> newValues;
{
auto oldValues = GetOldValues();
newValues.assign(oldValues.begin(), oldValues.end());
}