Question: I am working on my chess engine in C++ and I went for bitboards to represent the board. It is basically a 64-bit number for which I use the `bitset` library. I feel the most crucial part in performance comes through the move generation. Earlier I encoded moves as `std::string`'s in a `std::vector` which would perform moves on a 2-D array. A move has to include at the most basic, A start location, and an end location. That means for my purpose, two numbers would be enough. However, it shall also include two more attributes which are, castling rights(if any) en-passant squares.

One choice is to use a `struct` to represent two coordinates and the additional two attributes. And then create an array of those moves.

Another choice is to use a 16-bit number to represent a move. For example:
6-bits for the first number, 6-bits for the second. And the remaining for the additional values.
And then store them in an array/vector/any container.

Even a `std::vector<int>` can work.

The main purpose is to be able to iterate through the container efficiently as it will happen **multiple** times.

What is the best way to encode the move?

Answer: 
Questions like this usually boil down to a trade-off between memory usage and speed. A program which uses a highly packed representation for keeping boards in memory will always require some time to unpack the data into a form where it can evaluate and apply the game's semantics. This is especially true when this "unpacking" is done implicitly, by the accessor functions. Keeping boards and moves directly in an unpacked representation hence saves these CPU cycles.

However, in reality, the situation becomes more complex: if your program has to manage lots of boards and moves, and it is going to process millions of them, small-size representations may increase speed, since more boards and moves can be kept in main memory or CPU cache.

As a compromise for such scenarios, it is not uncommon to keep boards in a highly packed representation, unpack them explicitly when they are pulled from the queue or lookup-table (or whatever container your program uses for a tree search), process the unpacked representation to generate new boards and pack the results again to put them into the containers.

As you see, this is going to depend on the gory details of the algorithms of your program, and it may sometimes depend on the hardware on which the program is executed. Hence, if you want maximum speed, there is no way around

* implementing different approaches, and
* benchmark them!
You want to store enough information to make the move and to undo the move. So that is start position, end position, piece taken, and piece converted to at the very least. 6 bit + 6 bit + 3 bit + 2 bit.

If you want to take it to 16 bit at most, store 1 bit for "conversion" set if a pawn moves to the last row, and you can leave out either the 2 bit what the pawn was converted to, or you leave out 3 bits of the "end position" because it must be on the last row.