2

This example program does not compile, because the transform_view cannot be converted to a std::span:

class Foo {
private:
    std::vector<std::string> strings = { "a", "b", "c" };

public:
    std::span<const char*> getStrings() {
        return strings | std::views::transform([](const std::string& str) { return str.c_str(); });
    }
};

int main() {
    Foo foo;
    auto strings = foo.getStrings();

    for (auto s : strings)
        std::cout << s << std::endl;
}

I know that it is not possible to construct containers (like std::vector) yet, however I don't quite understand, why it is not possible to construct a std::span from it. I found this answer, that stated, that currently the only container that can be constructed from an arbitrary range is std::span, so I expected the above example to work.

Is there any way to create a span from a range? Or is there any other way to return a generic view from a method, without using auto (which is not allowed for virtual methods)?

Carsten
  • 11,287
  • 7
  • 39
  • 62
  • 1
    No `std::span` cannot be constructed from an arbitrary range. It is just a simple lightweight viewer over a contiguous range. – ALX23z May 12 '21 at 19:32

2 Answers2

6

Is it possible to construct a std::span from a view in C++20?

It is possible to construct a span from any contiguous range (of appropriate underlying type). The problem here:

std::span<const char*> getStrings() {
    return strings | std::views::transform([](const std::string& str) { return str.c_str(); });
}

is that the adapted range you're producing isn't contiguous, it's only random access. A span<char const*> has to refer to char const*s that are contiguous in memory, and that's definitely not going to be the case here. That's why this doesn't work.

But that doesn't mean that no adapted ranges can be converted to a span. For instance, this would work:

std::span<std::string> getStrings() {
    return strings | std::views::take(5);
}

Since views::take can preserve contiguity (in a way that transform cannot, for reasons that hopefully are clear).

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Thanks, that makes sense. How about the second part of my question though? I want to be able to pass a view back to the caller, however my method is actually part of an interface, so it's `virtual` and I cannot do something like `constexpr auto getStrings()`. Do I have to create and return an array or vector in this case? – Carsten May 12 '21 at 20:26
  • @Carsten You could return a `span` in your case, without the transform (which is just better than `span` anyway). Otherwise, a `vector` is probably a good bet. – Barry May 12 '21 at 22:28
  • thanks, I think `span` will do for most cases. I need to get the `const char*` array for some C API, though. However, I think in that case, I will use some conversion helper to facilitate a `vector` that stores the pointers then. Thank you! – Carsten May 13 '21 at 06:33
  • Question: If I interpreting std::span correctly, it's a non owning data structure. So does returning a std::span created from a temporary not end up really bad? – marsl Jun 02 '22 at 07:40
  • @marsl It depends. In `getStrings()` here, the lifetime of the `span` doesn't depend on the lifetime of the temporary that `views::take` provides, it only depends on the lifetime of `strings`. – Barry Jun 02 '22 at 12:20
1

std::span is a pointer and a size. It points into an array (or nowhere). There is no array of const char *.

You can't get a std::span from a std::deque either.

Caleth
  • 52,200
  • 2
  • 44
  • 75