Krzysztof Borowczyk
Krzysztof Borowczyk

Reputation: 1

static_assert always computes true (avr, g++)

I have some templated code for timers abstraction on my avr device. Most relevant part here is:

template <typename T>
class Timerx8bit
{
    T reg;

    static_assert(T::res == TimerResolution::bit8, "The timer template is 8bit, registers must also be 8bit");
}


struct Timer0
{
    constexpr static const TimerResolution res = TimerResolution::bit16;

    volatile uint8_t* tccra = &TCCR0A;
    volatile uint8_t* tccrb = &TCCR0B;
    //[...]
};

Now I feed the Timer0 to the template. The problem with that code is, that the static assert seems to evaluate always to true, although in the above situation it should fail. TimerResolution is just an enum class.

The problem seems to be in the template, if I put "TimerResolution::bit8 != TimerResolution::bit8" as the condition, compilation fails as expected, but "T::res != T::res" compiles without any problems... what am I missing here?

EDIT: While preparing full example of code I found the problem, although I still don't quite understand why it behaves that way. First, the code:

enum class TimerResolution_test
{
    bit8,
    bit16
};

struct Timer0_test
{
    constexpr static const TimerResolution_test res = TimerResolution_test::bit8;
};

template <typename T>
class Timerx8bit_test
{
public:
    constexpr static const TimerResolution_test res = TimerResolution_test::bit8;

private:
    T reg;

    static_assert(T::res != T::res, "The timer template is 8bit, registers must also be 8bit");
};

template<typename Timer>
class pwm_test
{

};

Instantiation:

pwm_test<Timerx8bit_test<Timer0_test>> testTimer; // Compiles
Timerx8bit_test<Timer0_test> testTimer2; // Fails

The second instantiation fails with the static_assert as above. If I put 'false' instead of the templated condition it fails in both cases... why is that? Shouldn't it fail in both cases with the original templated condition?

Upvotes: 0

Views: 409

Answers (1)

Templates don't require the complete type definition immediately (think CRTP). They must use the type in a way that will cause the complete type definition to be required. Your pwm_test doesn't use the type parameter it is given except by name. So the template body never needs to be instantiated.

In the second case you create an object, so naturally the template body instantiation happens.

So you need to force the instantiation by providing a context where it must happen:

template<typename Timer>
class pwm_test
{
  enum { _ = sizeof(Timer) };
};

On a related note, if you have any static assertions in a member function of a template, those won't be triggered until you add a call to the function in your code.


To answer your other question, why does static_assert result in an immediate error in one case but not the other:

§14.6/8 [temp.res]

Knowing which names are type names allows the syntax of every template to be checked. The program is ill-formed, no diagnostic required, if:

  • ...
  • a hypothetical instantiation of a template immediately following its definition would be ill-formed due to a construct that does not depend on a template parameter, or

So when encountering static_assert(false) while parsing the template, the compiler can deduce that all templates instantiations will be ill-formed. In this case it chooses to issue a diagnostic immediately (note that it doesn't have to).

When static_assert(T::res != T::res) is encountered, the compiler checks the syntax, but it cannot deduce that T::res != T::res is always false, that information is only available when T is known (after all, T::res can be something that overloads operator!= to always return true).

Upvotes: 1

Related Questions