Зачем тащить (CRC_TypeDef* crc) в интерфейс каждого метода? Шаблоны
так шаблоны. Пример. template<uint32_t PI2C>
class TI2C
{
public:
  constexpr TI2C() = default;
  
  template<I2CTiming TR=I2CTiming::APBCLK_100kHz>
  static inline void Init()
  {
    base()->TIMINGR = uint32_t(TR);
    base()->CR1 = I2C_CR1_PE;
  }
private:
  static inline auto base() { return (I2C_TypeDef *)PI2C; }
};
using i2c1 = TI2C<(uint32_t)I2C1_BASE>;
using i2c2 = TI2C<(uint32_t)I2C2_BASE>;
 Теперь можно i2c1 и i2c2 пользоваться напрямую
i2c1::Init<I2CTiming::SysCLK_1MHz>();
 Либо вешать на них другие устройства
using EEPROM = EE_24C<i2c1, EEType::AT24C512, EE_HW_ADR>;
 
И без всякой словесной шелухи куда проще читается.