How to Insert input directly into set container from input stream?
This is how I need
while(n--)
{
cin>>s.emplace();
}
Assume,I need to get n inputs and set container name is 's'
while(n--)
{
int x;
cin>>x;
s.emplace(x);
}
This works fine but I need to cut this step.
CodePudding user response:
Since C 20 you can use std::ranges::copy
, std::counted_iterator
, std::istream_iterator
,std::default_sentinel
and std::inserter
to do it. The counted_iterator
default_sentinel
make sure that at most n
elements are copied from the stream.
Example:
#include <algorithm> // ranges::copy
#include <iostream>
#include <iterator> // counted_iterator, default_sentinel, istream_iterator, inserter
#include <set>
#include <sstream> // istringstream - only used for the example
int main() {
// just an example istream:
std::istringstream cin("1 1 2 2 3 3 4 4 5 5");
int n = 5;
std::set<int> s;
std::ranges::copy(
std::counted_iterator(std::istream_iterator<int>(cin), n),
std::default_sentinel,
std::inserter(s, s.end())
);
for(auto v : s) std::cout << v << ' ';
}
The output will only contain 3 elements since the first 5 elements in the stream only had 3 unique elements:
1 2 3
Prior to C 20, you could use copy_n
in a similar fashion:
std::copy_n(std::istream_iterator<int>(cin), n, std::inserter(s, s.begin()));
Caution: If there are actually fewer than n
elements in the stream, the copy_n
version will have undefined behavior. Streams are notoriously unpredictable when it comes to delivering exactly what you want and copy_n
makes error checking really hard.
I made some observations in two of the major implementations of what undefined behavior can mean:
libstc
(gcc): If the stream is absolutely empty the above operation will insert one uninitialized element in theset
. Since anistream_iterator
(constructed with a stream) reads one element from the stream when it's constructed, one can check the state of the stream after constructing theistream_iterator
and everything seems to work fine:if(std::istream_iterator<foo> it(cin); cin) std::copy_n(it, n, std::inserter(s, s.end()));
Since I've never used
copy_n
on an empty stream before, but from streams with too few elements forcopy_n
, I have falsely believed that this operation was safe. The above seems to work no matter what I throw at it - but it's undefined behavior.libc
(clang): Makes a reference binding to null pointer and croaks when it tries to use that reference inoperator>>
.
I made a little test bench to explore what happens in these two implementations, and I might as well share that if someone else want to play with it: UB playground @ Compiler Explorer
CodePudding user response:
What you could do is create your own reusable function that provides as terse a syntax as you desire. You may as well put the loop in there too.
This way, you can even have correct error handling while keeping you main's code clean and simple.
A fully generic one could look like this:
#include <iostream>
#include <set>
#include <stdexcept>
template<typename ContainerT>
void populate(ContainerT& container, std::istream& stream, std::size_t n) {
using T = typename ContainerT::value_type;
while(n--) {
T tmp;
if(stream >> tmp) {
container.insert(container.end(), std::move(tmp));
}
else {
throw std::runtime_error("bad input");
}
}
}
int main() {
std::size_t n = 5;
std::set<int> s;
populate(s, std::cin, n);
}
You could even get a little fancier. For example, containers supporting reserve()
could have it called before starting to populate the container:
template<typename T>
concept HasReserve = requires(T x) {
{x.reserve(std::size_t{})};
};
// ...
ContainerT result;
if constexpr(HasReserve<ContainerT>) {
container.reserve(container.size() n);
}
//...