2

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.

Zeros
  • 83
  • 4
  • 3
    "I need to cut this step." Why? Alternatives will almost certainly get compiled to effectively the exact same executable. –  Nov 09 '21 at 15:41
  • No, if I get one line solution I can skip using braces for while loop. So I can write code faster – Zeros Nov 09 '21 at 15:43
  • 5
    Lines of code is a bad metric to optimize for speed. Typing the code is, like 10% (at the absolute most) of the time spent programming when you are doing anything non-trivial. Readable code will make you orders of magnitude faster. –  Nov 09 '21 at 15:45
  • 1
    @Frank more like 1% – Caleth Nov 09 '21 at 15:46
  • 1
    @Caleth Yeah, I'm being overly conservative/generous here. –  Nov 09 '21 at 15:49
  • 2
    @Frank It's 90% meetings :-) – Ted Lyngmo Nov 09 '21 at 15:55
  • “Measuring programming progress by lines of code is like measuring aircraft building progress by weight.” ― Bill Gates – François Andrieux Nov 09 '21 at 16:01
  • There's some subtlety in the above statement that I quite like. – user4581301 Nov 09 '21 at 16:14

2 Answers2

7

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 makes it copy n elements 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, both versions 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.

To make it safe, you could create a counting_istream_iterator to copy at most n elements from a stream using std::copy like this:

std::copy(counting_istream_iterator<foo>(cin, n),
          counting_istream_iterator<foo>{},
          std::inserter(s, s.end()));

Such an iterator could, based on std::istream_iterator, look something like this:

template<class T,
         class CharT = char,
         class Traits = std::char_traits<CharT>,
         class Distance = std::ptrdiff_t>
struct counting_istream_iterator {
    using difference_type = Distance;
    using value_type = T;
    using pointer = const T*;
    using reference = const T&;
    using iterator_category = std::input_iterator_tag;
    using char_type = CharT;
    using traits_type = Traits;
    using istream_type = std::basic_istream<CharT, Traits>;

    counting_istream_iterator() : // end iterator
        isp(nullptr), count(0) {}

    counting_istream_iterator(std::basic_istream<CharT, Traits>& is, size_t n) :
        isp(&is), count(n + 1)
    {
        ++*this; // read first value from stream
    }
    counting_istream_iterator(const counting_istream_iterator&) = default;
    ~counting_istream_iterator() = default;

    reference operator*() const { return value; }
    pointer operator->() const { return &value; }

    counting_istream_iterator& operator++() {        
        if(count > 1 && *isp >> value) --count;
        else count = 0; // we read the number we should, or extraction failed
        return *this;
    }
    counting_istream_iterator operator++(int) {
        counting_istream_iterator cpy(*this);
        ++*this;
        return cpy;
    }

    bool operator==(const counting_istream_iterator& rhs) const {
        return count == rhs.count;
    }
    bool operator!=(const counting_istream_iterator& rhs) const {
        return !(*this == rhs);
    }

private:
    std::basic_istream<CharT, Traits>* isp;
    size_t count;
    T value;
};
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
  • 4
    This is the answer to the question, but it is worth noting that it has drawbacks. It makes it much much harder to handle input errors. If `cin` reaches End Of File prematurely, you will get Undefined Behavior. It is also not possible to handle bad inputs, like inputs that are not convertible to `int`. This solution is brief and expressive but denies any practical error handling. I think the only way to handle errors is to enable exceptions for `cin` : [See this.](https://stackoverflow.com/questions/26187729/stdcin-doesnt-throw-an-exception-on-bad-input) But this has its own problems. – François Andrieux Nov 09 '21 at 15:56
  • To be clear, my previous comment is not a criticism of the answer. Rather, I'm trying to warn that the question's goal may not be worth the cost. – François Andrieux Nov 09 '21 at 15:59
  • @FrançoisAndrieux I'm glad you mentioned it. I was under the impression that this combo would be safe even in the advent of an early EOF - but, no, not when I think about it. Making that safe would need extra overhead. – Ted Lyngmo Nov 09 '21 at 16:08
  • It is safe if you use the range `std::istream_iterator(cin)` to `std::istream_iterator()` since the iterator becomes the end iterator when it reaches EOF. It is just unsafe here because of `std::copy_n` which assumes at least `n` elements and never checks against an `end` iterator. When you combine the EOF problem with the requirement to read `n` elements, I don't think there is a standard algorithm that solves both problems, I would have used `std::copy_n` as well. It would need to be a custom algorithm that accepts both a range and a maximum number of elements. – François Andrieux Nov 09 '21 at 16:11
  • @FrançoisAndrieux Yeah, I think I've gotten tricked in the past because I've observed copy_n read the elements available if the stream contains at least one element - but when I now tested with an empty istream, it inserted a bogus value. Typical UB. Checking the state of the stream before calling copy_n would help against that, but ... I'll play around with it some more later Tonight. – Ted Lyngmo Nov 09 '21 at 16:51
  • @FrançoisAndrieux I just found the C++20 addition that solves it - and that was only after I'd made my own `counting_istream_iterator` *sigh* :-) – Ted Lyngmo Nov 09 '21 at 21:29
  • Nice find for `std::counted_iterator`. Maybe you should post a second answer to this question using that solution. – François Andrieux Nov 09 '21 at 21:37
  • 1
    @FrançoisAndrieux I rewrote this answer. Many thanks for bringing this to my attention. Yet another illusion gone. :-) – Ted Lyngmo Nov 09 '21 at 21:56
  • @FrançoisAndrieux Bah ... this is a mess. This combination with the `counted_iterator` + `default_sentinel` isn't actually any better than my original. :-( – Ted Lyngmo Nov 09 '21 at 22:35
1

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);
}

//...