c++c++23

What is the difference between using `std::from_range` vs `std::ranges::to`?


C++23 introduces std::ranges::to, allowing the construction of a container from a range. E.g.,

auto my_set = produce_range() | std::ranges::to<std::set>();

And C++23 also introduces the std::from_range_t tag, which also allows the construction of a container from a range. E.g.,

auto my_set = std::set{ std::from_range, produce_range() };

Both are nearly the same amount of typing. Both are introduced in the same standard. Both produce the same result.

What is the difference between them? When should I use one rather than the other? I've used both quite a bit and they seem rather interchangeable.


Solution

  • What is the difference between them? When should I use one rather than the other? I've used both quite a bit and they seem rather interchangeable.

    The algorithm is std::ranges::to. That is the one you should use, all of the time. std::from_range is the implementation-detail customization point for that algorithm.

    If you want to express that you're taking the range from produce_range() and collecting all of the elements into a std::set with deduced type, the way you should write that is this:

    auto my_set = produce_range() | std::ranges::to<std::set>();
    

    That exactly expresses the intent of the code. to is a very commonly used terminal for range pipelines, so people will be familiar with it.

    The fact that it's terminal is also significant, since the typical use could easily have many adapters stacked on top of each other, and terminating (rather than wrapping) with to allows for a readable flow.

    auto my_set = some
                | sequence
                | of
                | adapters
                | followed
                | by
                | std::ranges::to<std::set>();
    

    auto my_set = std::set{ std::from_range, produce_range() };
    

    You should absolutely never write this (with braces).


    auto my_set = std::set( std::from_range, produce_range() );
    

    This alternative (with parentheses) does achieve the same thing as the original, it just requires more information of the destination type than is strictly necessary for you to have. Not all container types will even provide a std::from_range_t customization point, since not all of them will need to. So there's no benefit to writing this, outside of being (very slightly) terser.