1

I ran into a problem with setting a function as a default parameter.

The following code doesn't make a lot of sense. What I want to achieve can be done in many different ways. This code only describes the problem I ran into and wish to know how to fix it to work to my specifications.

#include <iostream>
#include <vector>

int double_the_number(int x)
{
    return x * 2;
}

template<typename T, typename FunctionType>
std::vector<FunctionType> copy_with_criteria(T iter1, T iter2, FunctionType F(FunctionType))
{
    std::vector<int> new_vector;
    while(iter1 != iter2)
    {
        new_vector.push_back(F(*iter1++));
    }
    return new_vector;
}

int main()
{
    std::vector<int> v {1,2,3,4,5};
    auto new_vector = copy_with_criteria(v.begin(), v.end(), double_the_number);
    for(int x : new_vector) std::cout << x << " ";
    return 0;
}

When the code above is ran, it will output 2 4 6 8 10

What I want to achieve is if I call a function without specifying the criteria function copy_with_criteria(v.begin(), v.end()) I want it to output 1,2,3,4,5

That is, somehow I would like to set the function as a default parameter which is a type of elements inside some container (in this case vector) and which returns number that has been sent to it, like this (TypeOfElements is just an example of what type the default criteria function should be):

TypeOfElements default_function(TypeOfElements x) {
    return x;
}

I would not like to use any external libraries. Also I am working with c++11.

If anyone could help me with this problem I would be very grateful!

Thank you :)

galaxyworks
  • 321
  • 1
  • 2
  • 10
  • Possible duplicate of [Default template arguments for function templates](http://stackoverflow.com/questions/2447458/default-template-arguments-for-function-templates) – didiz Apr 23 '17 at 19:58
  • Note your `copy_with_criteria` claims to return a `std::vector` but contains a hard-coded `std::vector`. – aschepler Apr 23 '17 at 20:02
  • Yes my mistake, I should have put that local vector with type of `FunctionType` – galaxyworks Apr 23 '17 at 20:03

3 Answers3

0

Overload your function.

template <typename T>
std::vector<typename std::iterator_traits<T>::value_type>
copy_with_criteria(T iter1, T iter2)
{
    return std::vector<typename std::iterator_traits<T>::value_type>(iter1, iter2);
}
aschepler
  • 70,891
  • 9
  • 107
  • 161
0

You can define your function as:

template<typename T, typename FunctionType = typename std::decay<decltype(*std::declval<T>())>::type>
std::vector<FunctionType> copy_with_criteria(T iter1, T iter2, FunctionType(*F)(FunctionType) = [](FunctionType v){ return v; }) {
    // ...
}

It works in C++11 as requested (see it on Coliru).
The basic idea is that you can deduce FunctionType directly from the type of the iterators and not from the function F. Then you can give to F a default by using a lambda function that is nothing more than an identity function.


Otherwise you can simply overload copy_with_criteria as suggested by aschepler (I'd rather go with his approach instead of using a default argument) or simply define a different function with a meaningful name that is explicit about your intention for you are not using criteria during that kind of copy.

Edit

As suggested by @aschepler in the comments, you can use iterator_traits<T>::value_type instead of typename std::decay<decltype(*std::declval<T>())>::type to avoid problems with some types.

Community
  • 1
  • 1
skypjack
  • 49,335
  • 19
  • 95
  • 187
  • Behaves dangerously if `T` is `std::vector::iterator`. – aschepler Apr 23 '17 at 20:27
  • @aschepler Can you go a bit deeper with your comment? It sounds interesting, but I don't fully get it. Why does it behave dangerously in that case? – skypjack Apr 23 '17 at 20:31
  • It returns a `std::vector::reference>`, which becomes invalid if the original vector is modified or goes out of scope. – aschepler Apr 23 '17 at 20:33
  • @aschepler Right, the reference, I didn't thought of it. Well, with a `std::enable_if_t` on the return type one can put a guard against it. Or use an overload as you suggested, that is the way suggested by the language. ;-) – skypjack Apr 23 '17 at 20:35
  • Or use `iterator_traits::value_type` to get `bool` instead of `std::vector::reference`. – aschepler Apr 23 '17 at 20:37
  • @aschepler Indeed. Can I use your comments to integrate the answer (with credits)? – skypjack Apr 23 '17 at 20:38
0

Functions are the wrong thing to use here. State can easily be useful. You want to use a generic function object. And deduce the return value.

In addition, using iterator ranges is questionable. The next iteration of C++ range library is going to reduce that use.

While you want a C++11 solution, there is no reason to use the C++03/C++11 style. Be forward looking.

So let us get started.

#include <iterator>
namespace notstd {
  namespace adl_helper {
    using std::begin; using std::end;
    template<class C>
    auto adl_begin( C&& )
    -> decltype( begin( std::declval<C>() ) );
    template<class T, std::size_t N>
    auto adl_begin( T(*)[N] )
    -> T*;
  }
  template<class C>
  using iterator_type=decltype(
    ::notstd::adl_helper::adl_begin( std::declval<C>() )
  );
}

This finds the iterator type of a container via calling std::begin in an ADL-enabled context. This emulates what a for(:) loop does reasonably well.

namespace notstd {
  template<class C>
  using value_type = typename std::iterator_traits<
    iterator_type<C>
  >::value_type;
}

now we can value_type<C> for some type C and get the type it contains.

namespace notstd {
  struct make_a_copy_t {
    template<class T>
    auto operator()(T&& t)const
    -> std::decay_t<T>
    {
      return std::forward<T>(t);
    }
  };
}

make_a_copy_t is a functor that copies stuff.

We are almost ready to solve your problem.

template<class Op=notstd::make_a_copy_t, class C,
  class R=decltype( std::declval<Op&>()(std::declval<notstd::value_type<C&>>()) )
>
std::vector<R>
copy_with_criteria(C&& c, Op op={})
{
  std::vector<R> new_vector;
  for (auto&& e:std::forward<C>(c))
  {
     new_vector.push_back( op(decltype(e)(e)) );
  }
  return new_vector;
}

and I believe this satisfies your criteria.

You may also need

namespace notstd {
  template<class It>
  struct range_t {
    It b = {};
    It e = {};
    It begin() const { return b; }
    It end() const { return e; }
    range_t( It s, It f ):b(std::move(s)), e(std::move(f)) {}
    range_t( It s, std::size_t count ):
      range_t( s, std::next(s, count) )
    {}
    range_t() = default;
    range_t(range_t&&)=default;
    range_t(range_t const&)=default;
    range_t& operator=(range_t&&)=default;
    range_t& operator=(range_t const&)=default;
    range_t without_front(std::size_t N)const {
      return {std::next(begin(), N), end()};
    }
    range_t without_back(std::size_t N)const {
      return {begin(), std::prev(end(),N)};
    }
    std::size_t size() const {
      return std::distance(begin(), end());
    }
    // etc
  };
  template<class It>
  range_t<It> range( It b, It e ) {
    return {std::move(b), std::move(e)};
  }
  template<class It>
  range_t<It> range( It b, std::size_t count ) {
    return {std::move(b), count};
  }
  template<class C>
  range_t<iterator_type<C&>> range( C& c ) {
    using std::begin; using std::end;
    return {begin(c), end(c)};
  }
}

which lets you do operations on subsections of a container as a range.

So suppose you want to take the first half of a vector of int and double it.

std::vector<int> some_values{1,2,3,4,5,6,7,8,9,10};
auto front_half = notstd::range(some_values).without_back(some_values.size()/2);
auto front_doubled = copy_with_criteria( front_half, [](int x){return x*2;} );

and done.

Live example.

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