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...
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++.