In C++20, instead of doing
size_t count = 42;
std::vector<int> my_vector;
vec.reserve(count);
std::copy_n(std::istream_iterator<T>(std::cin), count, std::back_inserter(vec));
I want to be able to just say
std::vector<int> my_vector = get_from_cin<int>(42);
Problem is, I also want to have a similar facility for std::list
:
std::list<int> my_list = get_from_cin<int>(42);
so I tried defining two function templates:
template<typename T>
std::vector<T> get_from_cin(size_t count) {
std::vector<T> vec;
vec.reserve(count);
std::copy_n(std::istream_iterator<T>(std::cin), count, std::back_inserter(vec));
return vec;
}
template<typename T>
std::list<T> get_from_cin(size_t count) {
std::list<T> list;
std::copy_n(std::istream_iterator<T>(std::cin), count, std::back_inserter(list));
return list;
}
int main() {
std::vector<int> my_vector = get_from_cin<int>(42);
}
which (expectedly) lead to ambiguity when specializing the template:
main.cpp:36:34: error: call to 'get_from_cin' is ambiguous
36 | std::vector<int> my_vector = get_from_cin<int>(42);
| ^~~~~~~~~~~~~~~~~
...
Godbolt link: https://godbolt.org/z/EbdxTEcns
How can I modify the code so that this ambiguity is eliminated and the resulting code for calling the templated function is as concise as possible?
PS. I would just name the two functions differently but that feels contrary to the C++ style.
If you can use C++23, then something like this can help:
#include <ranges>
#include <cassert>
auto vec = std::views::istream<int>(cin)
| std::views::take(42)
| std::ranges::to<std::vector>();
assert((size(vec) == 42));
The downside is however, that if user inputs 43 numbers instead, the 43th number will be dropped for ever. Special case of counted input iterator is an unsolved problem within the realms of std. The reliable solution is still reserve
followed by copy_n
. I don't approve of template<template>
syntax in user code, so I don't follow that route. But something like this can be useful:
template<std::ranges::range C>
requires (not std::ranges::view<C>)
auto from_istream(std::istream& ins, std::size_t n, auto&& ...args)
requires std::constructible_from<C, decltype(args)...>
{
C res{std::forward<decltype(args)>(args)...};
if constexpr (
requires (C cnt, std::size_t sz)
{ cnt.reserve(sz); }
) res.reserve(n + res.size());
std::copy_n
( std::istream_iterator
< std::ranges::range_value_t<C> >(ins)
, n, std::back_inserter(res));
return res;//nrvo copy elision.
};
Now you can instantiate it like this:
auto vec = from_istream<std::vector<int>>(cin,42);
assert((vec.size == 42));
auto lst = from_istream<std::list<int>>(cin,42);
In either case you need the type id of yor object on the right hand side of the assignment to instruct compiler what to create. Then type deduction and direct initialization handle the rest.