18

I have a template function inside which I want to generate a vector which is of an unknown type. I tried to make it auto, but compiler says it is not allowed.

The template function gets either iterators or pointers as seen in the test program inside the followed main function. How can the problem be fixed?

template<class Iter>
auto my_func(Iter beg, Iter end)
{
    if (beg == end)
        throw domain_error("empty vector");

    auto size = distance(beg, end);

    vector<auto> temp(size); // <--HERE COMPILER SAYS CANNOT BE AUTO TYPE
    copy(beg, end, temp->begin);
    .
    .
    return ....

}


int main()
{
    int bips[] = {3, 7, 0, 60, 17}; // Passing pointers of array
    auto g = my_func(bips, bips + sizeof(bips) / sizeof(*bips));

    vector<int> v = {10, 5, 4, 14}; // Passing iterators of a vector
    auto h = my_func(v.begin(), v.end());

    return 0;
}
Edgar Rokjān
  • 17,245
  • 4
  • 40
  • 67
axcelenator
  • 1,497
  • 3
  • 18
  • 42

7 Answers7

44

You cannot use a std::vector of auto. You might use std::iterator_traits instead:

std::vector<typename std::iterator_traits<Iter>::value_type> temp(size);
Edgar Rokjān
  • 17,245
  • 4
  • 40
  • 67
33

If you have a C++17-compatible compiler, you may profit from class template argument deduction.

So unless you have a specific reason to fill your vector with std::copy, you could write your code like this:

template<class Iter>
auto my_func(Iter beg, Iter end)
{
    if (beg == end)
        throw domain_error("empty vector");

    vector temp(beg, end);
    // do the remaining stuff
    return ....
}

If this feature is not available on your compiler, then I'd vote for

vector<typename iterator_traits<Iter>::value_type> temp(beg, end);

like in Jonathan's answer

Vasiliy Galkin
  • 1,894
  • 1
  • 14
  • 25
7

You might be looking for something like

std::vector<typename std::remove_reference<decltype(*beg)>::type> temp(beg, end);

Demo

mch
  • 9,424
  • 2
  • 28
  • 42
Igor Tandetnik
  • 50,461
  • 4
  • 56
  • 85
4

The reason auto doesn't work is because it's not allowed in that context. You may not provide auto in place of a template argument. The correct course of action when you want the compiler to deduce a template argument automatically is to not provide an argument at all. However, in this case, there is no way for the compiler to deduce what that type should be. You must provide the type explicitly.

There are many ways of finding out what the correct type for your vector is. You can use std::iterator_traits to obtain information about an iterator, including the type of value it refers to. You would use typename std::iterator_traits<Iter>::value_type.

#include <algorithm>
#include <iterator>
#include <stdexcept>
#include <vector>

template<class Iter>
auto my_func(Iter beg, Iter end)
{

    if (beg == end)
        throw std::domain_error("empty vector");

    auto size = std::distance(beg, end);

    using t_value = typename std::iterator_traits<Iter>::value_type;
    std::vector<t_value> temp(size);

    std::copy(beg, end, temp.begin());

    return temp;
}


int main()
{

    int bips[] = { 3,7,0,60,17 };//Passing pointers of array
    auto g = my_func(bips, bips + sizeof(bips) / sizeof(*bips));

    std::vector<int> v = { 10,5,4,14 };//Passing iterators of a vector 
    auto h = my_func(v.begin(), v.end());

    return 0;
}

I would like to point out that there is no reason to check for 0 size ranges. It would correctly return an empty vector.

You can also simplify the body of my_func quite a bit by taking advantage of the fact that std::vector has a constructor that accepts a pair of iterators and copies that range.

template<class Iter>
auto my_func(Iter beg, Iter end)
{
    using t_value =typename std::iterator_traits<Iter>::value_type;
    return std::vector<t_value>(beg, end);
}
François Andrieux
  • 28,148
  • 6
  • 56
  • 87
3

You can extract a pointer/iterator's type information using iterator_traits. value_type is the specific trait that you are interested in, so you can do:

const vector<typename iterator_traits<Iter>::value_type> temp(beg, end);

Live Example

Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • 1
    @tobi303 The comment no longer applies. The user changed the answer to make it work now. Now it is a duplicate answer of the top voted one so it should probably just be deleted. especially since it is missing the `std::` to qualify the names. – NathanOliver Jul 20 '17 at 14:28
2

It is not true that the type is not known. The type of the vector you want to create is of the same kind of Iter.

Just get iter Underlying type either using decltype or using iterator type trait as follows:

  • decltype -> std::vector<typename remove_reference<decltype(*beg)>::type> temp(beg, end);

  • iterator type trait

as follows

using Type = std::iterator_traits<Iter>::value_type;
std::vector<Type>...
Davide Spataro
  • 7,319
  • 1
  • 24
  • 36
0

I would solve this slightly differently than your question seems to be asking for.

First, I find ranges to be a better fundamental type than taking two iterators. The two iterators are coupled, they should be one argument. A range is a simple struct of two iterators, with some utility methods:

template<class It>
struct range_t:
  std::iterator_traits<It>
{
  It b{}, e{};
  It begin() const { return b; }
  It end() const { return e; }
  bool empty() const { return begin()==end(); }
  auto size() const { return std::distance(begin(), end()); }
  // etc
  range_t()=default;
  range_t(range_t const&)=default;
  range_t(range_t &&)=default;
  range_t& operator=(range_t const&)=default;
  range_t& operator=(range_t &&)=default;
};
template<class It>
range_t<It> make_range( It s, It f ) { return {std::move(s), std::move(f)}; }

range_ts correctly couple the begin end iterator together.

Now

template<class Range>
auto my_func(Range&& range) {
  // todo
}
template<class Iter>
auto my_func(Iter beg, Iter end)
{
   return my_func(make_range(std::move(beg), std::move(end)));
}

is the first step. Or just eliminate the two-iterator version entirely, and expect the caller to package up their iterators for you.

template<class Range>
auto my_func(Range&& range) {
  if (range.empty())
    throw domain_error("empty vector");
  // todo
}

Ok, now you want to do this:

auto size = range.size();

vector<auto> temp(size);//<--HERE COMPILER SAYS CANNOT BE AUTO TYPE 
copy(range.begin(), range.end(), temp->begin);

but that is a common operation. So we write it for range:

template<class Range>
auto as_vector( Range const& r ) {
  using value_type = typename Range::value_type;
  std::vector<value_type> v( range.begin(), range.end() );
  return v;
}

giving us:

template<class Range>
auto my_func(Range&& range) {
  if (range.empty())
    throw domain_error("empty vector");
  auto v = as_vector(range);
  // ...
  return ...;
}

We have decomposed your problem into simple primitives that have meaning, and moved implementation complexity into those primitives. The "business logic" of your my_func no longer cares what steps you take to make a range into a vector.

This makes your my_func more readable, so long as you have some trust that as_vector(range) actually returns that range as a vector.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524