Should there be a standard C++ pattern for this? transform_to

Raymond

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.

11 comments

Comments are closed. Login to edit/delete your existing comments

    • Jeremy Richards

      Wow. I know it has been a decade or so since I have done much C++. But I have tried to keep up on the innovations to the language, but I had no idea that they had essentially added LINQ in C++20. And naturally they decided to overload the bitwise OR to indicate piping rather than some just using normal functions or just keeping with the >> theme from iostreams.

      • Danstur

        Oh come on, who in their right mind would want c++ for once to follow established practices that work perfectly fine in all other languages most people have experience with if you could instead show everyone how awesome c++’s operator overloading is.

        The old joke interview with Stroustroup was prophetic really.

        • Pierre Baillargeon

          Good thing then that range adaptors can be used as functions, so you can use “follow established practices that work perfectly fine in all other languages most people have experience with”. The main downside with them is they preclude internet-bashing. You’ll have to make-do with being wrong.

          Beside, some functional languages do have similar pipeline operator. Bash and most shell in existence have had them for multiple decades, which should help you be familiar with the concept.

    • Adam Rowell

      Although this has the same caveat as

      var newValues = GetOldValues().Select(v => (int)v[0]);

      In that it will repeat the transform each time it is iterated. We’d need an

      actions::to

      or similar, to match the trailing

      .ToArray()

  • Remy Lebeau

    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());
    }

  • Valts Sondors

    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, but with objects and operator overloads. So that you can keep on working with modern collections with the same syntax you used on bare pointers 30 years ago.

    Right now they have all the tools in the toolbox to write a much more modern and cleaner interface, something akin to C# or Javascript, but they’re still keeping with the old traditions.

    Is there any effort out there to replace the C++ standard library with something more modern? (Of course they would need to coexist for a long, long time, but still…)

    • Neil Rashbrook

      Maybe the Web(kit) Template Framework or the Mozilla Framework Based on Templates would suit you? (If only because you like their initialisms…)

  • cheong00

    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.

  • Joshua Hudson

    I could write the transforming iterator but if G++ ever gets inner functions it would become mostly obsolete.