Timo
Timo

Reputation: 871

C++ partial class template specialization over multiple parameters

I'm trying to essentially define a template class that represents a hardware peripheral, which has some remappable pins. Since the mapping is defined at compile (or actually hardware schematic drawing) -time, I'd like to bring these defines in via template parameters. However, since each of the pins can be mapped independently of the others, the set of possible types is basically a cartesian product of the individual mappings, and I'm not sure if this can be made to work. What I have now is:

     enum class SPI1_NSS {
        PA4,
        PA15
     };

     enum class SPI1_SCK {
        PA5,
        PB3
     };

     template<SPI1_NSS nss_enum, SPI1_SCK sck_enum>
     struct SPI_1 {
        //...other stuff

        struct nss;
        struct sck;

     };

     template<SPI1_SCK sck>
     struct SPI_1<SPI1_NSS::PA4, sck>::nss {
        using pin = GPIOs::A::pin<4>;
     };

     template<SPI1_SCK sck>
     struct SPI_1<SPI1_NSS::PA15, sck>::nss {
        using pin = GPIOs::A::pin<15>;
     };

     template<SPI1_NSS nss>
     struct SPI_1<nss, SPI1_SCK::PA5>::sck {
        using pin = GPIOs::A::pin<5>;
     };

     template<SPI1_NSS nss>
     struct SPI_1<nss, SPI1_SCK::PB3>::sck {
        using pin = GPIOs::B::pin<3>;
     };

This fails with error: invalid class name in declaration of 'class HAL::SPI_1<HAL::SPI1_NSS::PA4, sckp>::nss' and similar errors for the others. It works if I remove one of the two template parameters.

What I'd expect to have is that, for example, given

    using spi = SPI_1<SPI1_NSS::PA4, SPI1_SCK::PB3>;

the type spi::nss::pin would be GPIOs::A::pin<4> and spi::sck::pin would be GPIOs::B::pin<3>. Is this kind of "Cartesian specialization" possible somehow?

I do realize that I could just template on the GPIO types directly, and this is a bit overengineered. However, the advantage I'd get from this is that the enum provides and guarantees only the valid choices for the pins, so it makes for a clearer interface.

Upvotes: 2

Views: 93

Answers (1)

If your intent is to specialize orthogonality, I'd use distinct meta-functions that aren't nested withing SPI_1

namespace detail {
  template<SPI1_NSS>
  stuct nss;

  template<>
  struct nss<PA4> {
    using pin = GPIOs::A::pin<4>;
  };

  template<>
  struct nss<PA15> {
    using pin = GPIOs::A::pin<15>;
  };

  // Same for sck
}

template<SPI1_NSS nss_enum, SPI1_SCK sck_enum>
struct SPI_1 {
   //...other stuff

   using nss = detail::nss<nss_enum>;
   using sck = detail::sck<sck_enum>;
};

Upvotes: 3

Related Questions