wtorek, 5 stycznia 2010

Asercje czasu kompilacji

Część z nas, korzystając z C++, pewnie pisała podobny kod:

#include <cassert>

int main()
{
    assert(sizeof(long long) == 8);
    return 0;
}


Ale przecież to można by sprawdzić już w czasie kompilacji - prawda?

W C++0x (w sumie teraz już C++1x, nawet na wiki zmienili ;)) będzie słowo kluczowe static_assert, które pozwoli robić takie rzeczy.

Ale jak sobie z tym poradzić zanim nowy C++ trafi pod strzechy?

Istnieje dyrektywa preprocesora #error, można to spróbować połączyć z #if:

int main()
{
    #if sizeof(long long) == 8
    #else
    #error Static assertion failed.
    #endif
    return 0;
}


Prezentowane powyżej "rozwiązanie" ma wiele wad:
1. Nie można go zapisać za pomocą prostego #define, potrzeba minimum 3 linijek kodu.
2. Tymi asercjami zajmuje się preprocesor, czyli są one sprawdzane na samym początku procesu kompilacji.
3. Z punktu 2 wynikają wielkie ograniczenia instrukcji #if - np. nie może być wykorzystywany operator sizeof (bo preprocesor nie wie nic o typach).

Czyli jest to słabe rozwiązanie.

Ale można wykorzystać magię C++, czyli szablony i ich konkretyzacje!

template<bool>
struct StaticAssert;

template<>
struct StaticAssert<true> { };


O co tu chodzi? Mamy definicję (bez deklaracji!) szablonowej struktury StaticAssert, parametryzowanej parametrem pozatypowym o typie bool. Mamy też jedną konkretyzację (pustą!) tego szablonu struktury dla parametru o wartości true.

Ale równie ważne jest to, czego nie ma - czyli drugiej konkretyzacji, dla false. Oznacza to, że jeżeli kompilator spróbuje utworzyć obiekt struktury StaticAssert<false>, to zgłoszony zostanie błąd kompilacji - brak przecież deklaracji dla takiej konkretyzacji tego szablonu.

Czyli otrzymujemy pożądaną asercję czasu kompilacji. Teraz tylko ładniej opakować:

#ifdef _DEBUG
#define STATIC_ASSERT(expr) (StaticAssert<(expr)>())
#else
#define STATIC_ASSERT(expr)
#endif


Makro STATIC_ASSERT ułatwia korzystanie z tej konstrukcji, a także gwarantuje, iż pojawi się ona w kodzie tylko w trybie "debug" (przynajmniej w Visual C++, użytkownicy g++ muszą pewnie trochę zmienić ten #if). W StaticAssert<(expr)>() nawiasy otaczające expr są istotne, bez nich operator ">" używany w warunkach byłby błędnie interpretowany.

Voila!

int main()
{
    STATIC_ASSERT(sizeof(long long) == 8);
    return 0;
}


Niestety to rozwiązanie też nie jest pozbawione wad - musi być wykorzystywane tylko w miejscach, w których można umieszczać instrukcje programu. Nie można napisać np.

template<class T>
struct Check
{
    STATIC_CHECK(sizeof(T) > sizeof(char));
};


Ale i tak jest nieźle ;)

Więcej "C++ heavy wizardry" w książce Andreia Alexandrescu "Modern C++ Design".

Brak komentarzy:

Prześlij komentarz