1

For practicing purposes I wanted to create a function similar to std::transform():

template<class Tin, class Tout>
std::vector<Tout> map( const std::vector<Tin>& in,
                       const std::function<Tout(const Tin&)>& mapper ) {

   std::vector<Tout> ret;
   for( auto elem : in ) {
       ret.push_back( mapper( in ) );
   }

   return ret;
}

and I intended it to use it as follows:

std::vector<Bar> bars /* = ... */;
std::vector<Foo> foos = map( bars, []( const Bar& bar ) { return bar.to_foo(); } );

However, I get undefined references for the function call. What is the correct signature for my map() function?

*Update: * Here's the actual error message (Bar = std::string, Foo = IPv6 (own class))

config.cc:98:61: error: no matching function for call to ‘map(const std::vector<IPv6>&, InterfaceConfig::set_ip6(const std::vector<IPv6>&)::<lambda(const IPv6&)>)’
config.cc:98:61: note: candidate is:
utils.h:38:31: note: template<class Tin, class Tout> std::vector<Tout> utils::map(const std::vector<Tin>&, const std::function<Tout(const Tin&)>&)

And here's the call: std::vector strings = utils::map( ips, []( const IPv6& ip ) { return ip.to_string(); } );

1
  • Please show us the exact error message. Commented Jul 18, 2013 at 10:23

2 Answers 2

4

There is two things in your code that will not work.

  • First, when passing a lambda function as argument, I suggest using Template. The standard library on Microsoft seems to use this method for std::for_each for example.
  • And :

    When function template has a return type, which cannot be deduced from arguments, or when function template doesn't have any argument, the type cannot be deduced by the compiler. This function will require template type argument specification.

Take a look at this example :

template<class Tout, class Tin, class Fun>
//                            ^^^^^^^^^^^
// Note that I changed the order of the types
std::vector<Tout> map( const std::vector<Tin>& in,
                       Fun mapper ) {
//                     ^^^^^^^^^^
   std::vector<Tout> ret;
   for( auto elem : in ) {
       ret.push_back( mapper( elem ) );
   }

   return ret;
}

int main()
{
    std::vector<int> bars /* = ... */;
    std::vector<float> foos = map<float>( bars, []( int ) { return 1.0f; } );
    //                           ^^^^^^^ Specify the type Tout
    system( "pause" );
    return 0;
}

EDIT :

Like it is said in the comment, we can use decltype and std::decay to not have to explicitly specify the result of the function :

  template<class Tin, class Fun> // no Tout
//                  ^^^^^^^^^^^
  auto map( const std::vector<Tin>& in, Fun mapper )
//^^^^                                  ^^^^^^^^^^
    -> std::vector<typename std::decay< decltype( mapper( in.front() ) )>::type > {

   std::vector<typename std::decay< decltype( mapper( in.front() ) )>::type > ret;


   for( auto elem : in ) {
       ret.push_back( mapper( elem ) );
   }

   return ret;
}

int main()
{
    std::vector<int> bars /* = ... */;
    std::vector<float> foos = map( bars, []( int ) { return 1.0f; } );
    //                           No specification
    system( "pause" );
    return 0;
}

Let's explain a little bit.

First we will use the late-specified return type syntax. It will allow us to use the parameter names in the return type specification. We start the line with auto and put the return type specification after the parameters using ->.

We will use decltype because the decltype type specifier yields the type of a specified expression. It will be very useful in our case. For example to get the type of the function we passed in parameters, it is just decltype( f( someArg ) ).

Let's state what do we want : The return type of the function should be a vector of the return type of the function passed in argument right ? So we can return std::vector< decltype( mapper( in.front() ) )> and that's it ! (Why the in.front() ? We have to pass a parameter to the function to have a valid expression.)

But here again, we have a problem : std::vector does not allow references. To be certain that it will not be a problem for us, we will use the std::decay meta-function who applies lvalue-to-rvalue, array-to-pointer, and function-to-pointer implicit conversions to the type T, removes cv-qualifiers, remove references, and defines the resulting type as the member typedef type.. That is, if the function returns something like const Foo& it will end in Foo.

The result of all of that : std::vector< typename std::decay< decltype( mapper( in.front() ) )>::type >.

You have to repeat this expression again at the beginning of the function to declare the variable you will return.

Some usefull references about that :

It is not easy to explain, I hope my explanations are understandable.

Sign up to request clarification or add additional context in comments.

3 Comments

You should change the return type to std::vector< decltype( mapper(in.front()) ) > with maybe some std::decay for taste at least in some cases. Maybe have the default type Tout be void, and if it is void use the above expression, and otherwise use std::vector<Tout>.
I had to re-read your post a couple of times to get it, but now I do. And thanks for the links as well, they were helpful.
@DeX3 It was hard to explain, there is a lot of new notions in this little piece of code :)
0

There's no need to explicitly specify the result of map, it can be deduced. I'm also going to accept any range (something that provides begin and end), simply because doing so is trivial. I could make it even more generic and use the free begin and end versions, but that makes it even more complicated, so I won't.

template <typename Range, typename Func>
auto map(const Range& r, Func f)
    -> std::vector<typename std::decay<decltype(f(*r.begin()))>::type> {
  std::vector<typename std::decay<decltype(f(*r.begin()))>::type> result;
  for (const auto& e : r) {
    result.push_back(f(e));
  }
  // Alternatively:
  //std::transform(r.begin(), r.end(), std::back_inserter(result), f);
  return result;  
}

This isn't exactly trivial code, so let me explain.

First, I use the late-specified return type syntax here: I start the function with auto and put the actual return type after the parameters, indicated with ->. This allows me to use parameter names in the return type specification, which is very useful in the decltype stuff I'm doing next.

So what do we actually want? We want a vector of whatever f returns when called with elements of r. What is that? Well, we can use decltype to find out. decltype(expr) gives you the type of expr. In this case, the expression is a call to f: decltype(f(arguments)). We have one argument: an element of the range. The only things a range gives us are begin() and end(), so let's use that: dereference begin() to get the actual value. The expression is now decltype(f(*r.begin())). Note that this is never actually evaluated, so it doesn't matter if the range is empty.

Ok, this gives us the return type of the function. But if we write std::vector<decltype(...)> that leaves us with a problem: the return type of the function could be a reference, but a vector of references is not valid. So we apply the std::decay metafunction to the return type, which removes references and cv-qualifiers on the referenced type, so if the function returns const Foo&, the result of std::decay is just Foo.

This leaves me with the final return type std::vector<typename std::decay<decltype(f(*r.begin()))>::type>.

And then you get to repeat the same thing to declare the actual variable holding the return value. Unfortunately, because there's no way to put type aliases at any reasonable point, you can't get rid of this.


Anyway, there was another problem with your original code, and that was declaring the loop variable as auto. If you call this map with a vector<Bar>, you end up with the loop

for (Bar b : in) { ... }

Notice how your loop variable is a value? This means that you copy every element of in to a local variable. If a Bar is expensive to copy, this is a serious performance issue. And if your transformation relies on the object identity of its argument (e.g. you return a pointer to a member), then your performance issue has become a correctness issue, because the resulting vector is full of dangling pointers. This is why you should use const auto& in the loop, or just use the std::transform algorithm internally, which gets this right.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.