4

I'm practicing C++, so this is not code that will go into production, but I'm very curious to know:

I have a vector of pointers to objects of type Player:

std::vector<Player*> _players;

Each player returns me his name as std::string when I call it's method get_name(), for example:

std::string player0_name = _players[0]->get_name();

I want to pass all player names to a function that expects them as reference to a vector of strings:

void all_player_names( std::vector< std::string >& );

Now I know it would be easy to do this via some temporary variable. I could first create a vector of strings, store all player names there, then pass it to the function all_player_names as reference.

But I'm looking for a way of doing that without having to create a temporary variable. It should be something similar like a list comprehension in Python. It has to iterate over the array of Player pointers, call the get_name() function on each of them, build a vector out of the returned strings and directly pass it to the function all_player_names. I'm assuming it should be possible with lambda functions and some algorithm from the STL, but I don't know which one.

So it should look more or less like that:

all_player_names(<function that i'm looking for>(
    _players, [](Player* p) { return p->get_name();}
);

Is there such an algorithm in the STL?

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
replay
  • 3,569
  • 3
  • 21
  • 30
  • _build a vector out of the returned strings_ ... that *is* a temporary variable, and *not* a comprehension. A list comprehension would be something more like an iterator pair, where dereferencing the iterator generates values on the fly – Useless Nov 14 '13 at 08:37
  • To be honest, looking at the answers below, I would prefer the for-loop. I mean, it still doesn't come close to `var names = players.Select(p => p.Name)` :-) – CompuChip Nov 14 '13 at 09:26

4 Answers4

5

Easiest way would be using accumulate and using a temporary variable that will be passed through the binary op and returned:

std::accumulate(_players.begin(), _players.end(), 
 std::vector<std::string>(),
 [](std::vector<std::string>& vector, const Player* elem) 
   {
    vector.push_back(elem->get_name());
    return vector;
   });

Thanks to move semantics it should have almost no performance overhead in C++11.

Johan
  • 3,728
  • 16
  • 25
  • you should create the vector with the empty constructor if you plan to call `push_back` like you do. – Ivaylo Strandjev Nov 14 '13 at 08:59
  • 2
    +1 your solution is good. Still please indicate in the answer that you still create a temporary variable and pass it as argument. There is no way to avoid this with this function declaration. – Ivaylo Strandjev Nov 14 '13 at 09:04
4

In my opinion it is better to use standard algorithm std::transform instead of std::accumulate. The semantic of std::transform is more clear in this case compared with the semantic of std::accumulate. For example

    struct Player
    {
    public:
        Player( const std::string &s ) : s( s ) {}
        const std::string & get_name() const
        {
            return s;
        }
    private:
        std::string s;
    };

    std::vector<Player *> v1;

    v1.push_back( new Player( "Hello" ) );
    v1.push_back( new Player( "Player" ) );

    std::vector<std::string> v2;
    v2.reserve( v1.size() );


    std::transform( v1.begin(), v1.end(), std::back_inserter( v2 ), 
                    std::mem_fun( &Player::get_name ) );

    for ( const std::string &s : v2 ) std::cout << s << ' ';
    std::cout << std::endl;
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
0

If you have an argument that is const ref you have no option but to construct a variable. Your const ref should point to some existing object. You can change your function to take a pointer to function and a const ref to std::vector<Player*>. Make the function take a pointer to Player and return his name.

Ivaylo Strandjev
  • 69,226
  • 18
  • 123
  • 176
0

What you want is to use lambda-expression (or anonymous function).

With C++11 is now possible, but not with previous version. Problem is treated also here.

In your case i would use a method in a new class Players:

class Players {
public:
    void addPlayer(Player*);
    void removePlayer(Player*);
    vector<string> getNames() {
      std::vector<string> names;
      for(unsigned int i = 0; i != players_.size(); ++i) {
        names.push_back(players_[i]->getName());
      }
    return names;
    }
private:
vector<Player*> players_;

};

Community
  • 1
  • 1
Luca Davanzo
  • 21,000
  • 15
  • 120
  • 146
  • 1
    I somehow fail to see the part of the code that did not work in pre - c++11. Also I don't quite see how this solves OP's problem. – Ivaylo Strandjev Nov 14 '13 at 09:06