Consider the code:
#include <iostream>
#include <vector>
#include <ranges>
class TA
{
private:
int value;
public:
TA(int _value) : value(_value)
{
std::cout << "Constructor TA, value = " << value << '\n';
}
TA(const TA& a)
{
value = a.value;
std::cout << "Copy constructor TA, value = " << value << '\n';
}
TA(TA&& a) noexcept
{
value = a.value;
std::cout << "Move constructor TA, value = " << value << '\n';
}
~TA()
{
std::cout << "Destructor TA, value = " << value << '\n';
}
};
void rvalue_vector_test1()
{
std::vector a{ std::from_range, std::vector{TA(1), TA(2), TA(3), TA(4), TA(5)} };
}
void rvalue_vector_test2()
{
std::vector a{ std::from_range, std::vector{TA(1), TA(2), TA(3), TA(4), TA(5)} | std::views::as_rvalue };
}
void iota_test1()
{
std::vector a{ std::from_range,
std::views::iota(1,6) |
std::views::transform([](auto elem) {return TA(elem); }) };
}
int main()
{
rvalue_vector_test1();
rvalue_vector_test2();
iota_test1();
}
Could anybody explain why in the code:
std::vector a{ std::from_range, std::vector{TA(1), TA(2), TA(3), TA(4), TA(5)} | std::views::as_rvalue }
it is necessary to use std::views::as_rvalue
to prevent copying (although std::vector{TA(1), TA(2), TA(3), TA(4), TA(5)}
is rvalue), but in the code:
std::vector a{ std::from_range,
std::views::iota(1,6) |
std::views::transform([](auto elem) {return TA(elem); }) };
there is no need to use std::views::as_rvalue
(P.S. I know that the best way is to write:
std::vector<TA> a{ std::from_range, std::views::iota(1,6) }
– there is no copying and moving at all, but I want to know, how constructors with std::from_range
work)
It has to do with the value category you obtain from dereferencing the iterator.
For std::vector
, after the declaration std::vector<TA> v;
, both *std::ranges::begin(v)
and *std::ranges::begin(std::vector<TA>{})
return lvalues. This is because std::ranges::begin
in this case simply forwards to std::vector<TA>::begin()
and the latter is not ref-qualified. If std::vector<TA>::begin()
were made to be ref-qualifed, a lvalue vector
and an rvalue vector
would have to return different iterator types, which would likely break many things.
For std::views::iota(1,6) | std::views::transform([](auto elem) {return TA(elem); })
, the signature of std::views::transform_view<...>::iterator::operator*
uses decltype(auto)
as the return type, which means it forwards both the type and the value category of the underlying transform functor. Since your lambda returns by value, the value category you obtain by dereferencing std::ranges::begin(...)
is rvalue, and move constructors are eligible.
Basically your example is unable to demonstrate what it the purpose of std::views::as_rvalue
. To demonstrate that you need objects which are none trivially movable.
So your TA
which just holds int
is to trivial. After altering your code so TA
holds std::string
difference becomes more obvious:
using std::filesystem::path;
void dlog(const std::source_location& s, const std::string& value, const std::source_location& a = std::source_location::current())
{
std::print("{} {}:{} called from {}:{} value={}\n",
a.function_name(), path { a.file_name() }.filename().string(), a.line(),
path { s.file_name() }.filename().string(), s.line(),
value);
}
void dlog(const std::string& value, const std::source_location& a = std::source_location::current())
{
std::print("{} {}:{} value={}\n",
a.function_name(), path { a.file_name() }.filename().string(), a.line(),
value);
}
class TA {
private:
std::string value;
public:
TA(int _value, const std::source_location& s = std::source_location::current())
: value(std::format("long text to skip SSO: {}", _value))
{
dlog(s, value);
}
TA(const TA& a, const std::source_location& s = std::source_location::current())
: value { a.value }
{
dlog(s, value);
}
TA(TA&& a, const std::source_location& s = std::source_location::current()) noexcept
: value { std::move(a.value) }
{
dlog(s, value);
}
~TA()
{
dlog(value);
}
};
Check logs this code produces.
Now version with std::views::as_rvalue
isntead coping string it moves then from one vector to other. This means much less allocation and deallocation which are quite expensive.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With