Search code examples
c++templateschess

How can I de-duplicate code without declaring an additional function in C/C++?


I am developing a chess engine and am working on move generation. For example, here is my function for generating moves for the black knight:

/** Pseudolegal moves don't take check into account. */
std::vector<uint8_t>
generate_pseudolegal_bknight_moves(std::shared_ptr<Position> position,
                                   uint8_t square) {
  assert(VALID_SQUARE(square));
  assert(position->mailbox[square] == B_KNIGHT);

  uint8_t candidates[8] = {
      NEXT_RANK(PREV_FILE(PREV_FILE(square))),
      NEXT_RANK(NEXT_RANK(PREV_FILE(square))),

      PREV_RANK(PREV_FILE(PREV_FILE(square))),
      PREV_RANK(PREV_RANK(PREV_FILE(square))),

      NEXT_RANK(NEXT_FILE(NEXT_FILE(square))),
      NEXT_RANK(NEXT_RANK(NEXT_FILE(square))),

      PREV_RANK(NEXT_FILE(NEXT_FILE(square))),
      PREV_RANK(PREV_RANK(NEXT_FILE(square))),
  };
  std::vector<uint8_t> moves;

  for (int i = 0; i < 8; i++) {
    uint8_t candidate = candidates[i];
    uint8_t piece = position->mailbox[candidate];
    if (VALID_SQUARE(candidate) && (!IS_BLACK_PIECE(piece))) {
      moves.push_back(candidate);
    }
  }

  return moves;
}

The function for generating white knight moves is very similar, with only two terms (macros) changing: B_KNIGHT -> W_KNIGHT, and IS_BLACK_PIECE -> IS_WHITE_PIECE.

I'd prefer not to essentially duplicate the move generation function for every piece, but have been doing it this way so far because it has the smallest runtime overhead. I could include bool is_white or something in the args and switch terms with a ternary is_white ? W_KNIGHT : B_KNIGHT, but the conditional would add an overhead during runtime that wasn't there previously, and it doesn't seem that elegant. I was wondering if there is some compile-time feature that would help me have one function definition.

I suppose I also could use inline functions to try and reduce the amount of duplicated code, but I was wondering if there was any alternative better than that.


Solution

  • If you don't want overhead you can use a template parameter and if constexpr:

    enum class Color { WHITE, BLACK };
    
    template <Color C> std::vector<uint8_t>
    generate_pseudolegal_knight_moves(std::shared_ptr<Position> position,
                                      uint8_t square) {
      ...
      if constexpr (C == Color::WHITE) {
        assert(position->mailbox[square] == W_KNIGHT);
      } else {
        assert(position->mailbox[square] == B_KNIGHT);
      }
      ...
    }
    
    // Call
    auto moves = generate_pseudolegal_knight_moves<Color::WHITE>(...);
    

    The standard guarantees the condition will be evaluated on compile time, and the false branch will be discarded.