Search code examples
c++c++17c++20range-v3

Can't pipe split_view over a string into transform(to<string>)


(Including both and just in case the solution/workaround is different.)

The code below works,

#include <cassert>
#include <range/v3/algorithm/equal.hpp>
#include <range/v3/range/conversion.hpp>
#include <range/v3/view/split.hpp>
#include <range/v3/view/transform.hpp>
#include <string>
#include <vector>

using namespace ranges;
using namespace ranges::views;

int main() {

    auto to_string =
#if 0
        to<std::string>
#else
        [](auto x){ return to<std::string>(x); }
#endif
        ;
    std::string str{"hello world"};

    auto strs = str | split(' ');

    assert(to_string(*strs.begin()) == "hello");
    assert(equal(str | split(' ') | transform(to_string),
                std::vector{"hello", "world"}));
}

but switching the 0 to 1 in the #if branch makes it fail.

What is the lambda doing differently than the to<std::string> object itself?


Solution

  • What is the lambda doing differently than the to<std::string> object itself?

    to<std::string> is not an object, it is actually an instantiated function pointer, which takes a default-constructible tag and returns a closure object that records the specified container type (i.e. std::string in your example).

    So its type can be roughly spelled as

    /* some range adaptor closure type */ (*)(tag_t)
    

    To make r | to<std::string> work, range-v3 additionally defines a pipe operator inside the tag_t that accepts this function pointer type, roughly as follows:

    struct tag_t
    {
      friend Container
      operator|(R&& r, /* some range adaptor closure type */ (*)(tag_t)) {
        return /* Immediately default-constructs and invokes a callable object */
      }
    };
    

    In your example, when the #if 0 branch is enabled, to_string is a function pointer that receives tag_t. This is why you get an error when invoking to_string(*strs.begin()), because *strs.begin() returns a range, which cannot be converted to tag_t.

    To make the above work, you can use to<std::string>()(note the parentheses here)to get a range adaptor closure object directly.


    It is worth noting that C++23 std::ranges::to does not currently support the syntax for omitting parentheses. The standards committee has discussed whether to support this feature (see issues/527), but unfortunately, it was rejected recently.