Search code examples
c++templatesmicrocontrolleravr

How do you pass a templated class instance as a template parameter to another template?


I have a class template and I want to pass instances of it as a template parameter to anther class template. Such as:

typedef Pin<(uint16_t)&PORTB,0> B0;
typedef Pin<(uint16_t)&PORTB,1> B1;

Then I would like to pass them like:

Indicator<B0,B1> Ind1;

The pin class template that I'm using:

template <uint16_t tPort, uint8_t tBit>
class Pin
{
public:
    static constexpr uint16_t Port = tPort;
    static constexpr uint16_t DDR = tPort-1;
    static constexpr uint16_t PIn = tPort-2;
    static constexpr uint8_t Bit = tBit;

    static constexpr void  Toggle() 
    {
        *reinterpret_cast<uint16_t*>(Port) ^= (1<<Bit);
    }

    static constexpr void PullHigh() 
    {
        *reinterpret_cast<uint16_t*>(Port) |= (1<<Bit);
    }

    static constexpr void PullLow() 
    {
        *reinterpret_cast<uint16_t*>(Port) &= ~(1<<Bit);
    }

    static constexpr void SetOutput() 
    {
        *reinterpret_cast<uint16_t*>(DDR) &= ~(1<<Bit);
    }

    static constexpr void SetInput() 
    {
        *reinterpret_cast<uint16_t*>(DDR) |= (1<<Bit);
    }

    static constexpr void SetHighImpedance() 
    {
        *reinterpret_cast<uint16_t*>(Port) &= ~(1<<Bit);
        *reinterpret_cast<uint16_t*>(DDR) &= ~(1<<Bit);
    }

    static constexpr bool Read() 
    {
        return (*reinterpret_cast<uint16_t*>(PIn) & (1<<Bit));
    }
};

I've been able to pass them to template functions. I assume template template arguments may be the answer. But have not been able to get it to work...


Solution

  • Non type template arguments are not limited to integers. You seem to pass a uint16_t only to reinterpret it as a pointer. Instead, you can pass the pointer itself as a template parameter.

    Also, note that reinterpret_cast are not allowed in constexpr context.

    Passing pointers at compile time would look like this:

    template <uint16_t* tPort, uint8_t tBit>
    class Pin
    {
        // ...
    };
    

    It would be used like this:

    using B1 = Pin<&PORTB, 1>;
    

    Assuming you want to write the Indicator template class, it would simply look like this:

    template<typename P1, typename P2>
    struct Indicator {
        // ...
    };
    

    If you're concerned about enforcing P1 and P2 to be pins, it can be done by making a type trait and asserting on it:

    // Base case
    template<typename>
    struct is_pin : std::false_type {};
    
    // Case where the first parameter is a pin
    template <uint16_t* tPort, uint8_t tBit>
    struct is_pin<Pin<tPort, tBit>> : std::true_type {};
    

    Then, use your asserts:

    template<typename P1, typename P2>
    struct Indicator {
        static_assert(is_pin<P1>::value && is_pin<P2>::value, "P1 and P2 must be pins");
    
        // ...
    };
    

    Then, to make a function that receive an Indicator, you can do the following:

    // Pass type only, and use static members
    template<typename IndicatorType>
    void do_stuff() {
        IndicatorType::stuff();
    }
    
    // Pass an instance of the class
    template<typename IndicatorType>
    void do_stuff(IndicatorType indicator) {
        indicator.stuff();
    }
    

    These functions are invoked like this:

    // Passing only the type
    do_stuff<Indicator<B1, A1>>();
    
    // Passing an instance
    Indicator<B1, A1> indicator;
    
    do_stuff(indicator);
    

    This time I wouldn't be worried about IndicatorType not being an indicator. Any class that act as an indicator willl be accepted, and if cannot be used the same way as an indicator, then a compile-time error occurs. This will allow more flexibility about how indicators are implemented.

    Also, I suggest you to read more, or more in depth tutorial about templates in C++. Sometimes overlooked, it's one of the most important and complex feature of C++.