/*
* Maciej Lech, grupa F1ISIII
* 
* Praca domowa z przedmiotu ZPR: Opis i przykład użycia Boost Static Assert.
*
* Przykład zakłada korzystanie z bibliotek Boost w wersji 1.38
*
* UWAGA:
* Jako, że ten przykład ma pokazywać makro działające w czasie kompilacji, oraz
* powodujące błędy kompilacji, zawiera on fragmenty kompilowane warunkowo.
* Gdy zdefiniujemy (za pomocą -Dmakro dla gcc, bądź /Dmakro dla cl.exe) odpowiednie
* makro, spowodujemy błąd kompilacji opisany w odpowiadającym mu fragmencie tego
* przykładu. Ma to na celu zapoznanie z komunikatami kompilatora, jakie ten wypisuje,
* gdy Boost Assert otrzyma wartość fałszywą (ale też inne błędy wynikające, np. z błędnego
* użycia Boost Assert).
* Oczywiście, odsyłam po inne przykłady do dokumentacji makra:
* http://www.boost.org/doc/libs/1_38_0/doc/html/boost_staticassert.html
*/

#include <boost/static_assert.hpp>
#include <cassert>
#include <limits>
#include <iostream>

/*
	* Biblioteka Boost Static Assert udostępnia pojedyncze
	* makro o nazwie BOOST_STATIC_ASSERT. Makro to jest
	* analogiczne do popularnego makra assert ze standardowej
	* biblioteki języka C z tą jednak podstawową różnicą,
	* że działa w czasie kompilacji kodu, a nie jego wykonania.
	* W przypadku, gdy wartość wyrażenia podanego jako argument
	* makra jest fałszem, BOOST_STATIC_ASSERT powoduje błąd
	* kompilacji. Należy zaznaczyć, że wartość argumentu makra
	* musi dać się obliczyć w trakcie kompilacji, żeby makro
	* zadziało. W związku z tym poniższa konstrukcja zakończy się
	* błędem kompilacji (i to bynajmniej nie wywołanym intencjonalnie
	* przez BOOST_STATIC_ASSERT):

	//...
*/

void funcWithI() {

	int i = 5;

	// rób coś z i być może zmieniając jego wartość...

#ifdef BOOST_STATIC_ASSERT_ERROR1
	BOOST_STATIC_ASSERT(i > 0); // błąd - wartość i nieznana podczas kompilacji
#else
	assert(i > 0); // OK
#endif
}

/*
	//...

	* W powyższym kodzie powinno zostać użyte makro assert, gdyź ewidentnie
	* chcemy sprawdzić wartość dostępną jedynie w czasie działania programu.
	* Aby wywołać błąd kompilacji tego przykładu wynikający z próby użycia makra
	* BOOST_STATIC_ASSERT dla wyrażenia nie dającego się obliczyć w czasie
	* kompilacji należy skompilować ten plik ze zdefiniowanym makrem
	* BOOST_STATIC_ASSERT_ERROR1.
	*
	* Makro BOOST_STATIC_ASSERT można umieścić wszędzie tam, gdzie ,można
	* umiesczać deklaracje, a więc w przestrzeniach nazw, w klasach, funkcjach.
	*
	* *************************************************************************
	* BOOST_STATIC_ASSERT w przestrzeniach nazw
	* *************************************************************************
	* BOOST_STATIC_ASSERT w zasięgu przestrzeni nazw jest użyteczne, gdy chcemy
	* sprawdzić, czy platforma, na której dokonywana jest kompilacja naszego
	* programu spełnia określone wymagania. Poniżej chcemy zagwarantować, że
	* platforma, dla której kompilujemy kod ma typ long long int długości co najmniej
	* 32 bitów.
*/

namespace boost_static_assert_space {

	BOOST_STATIC_ASSERT(sizeof(unsigned long int) >= 4);
}

/*
	*
	* *************************************************************************
	* BOOST_STATIC_ASSERT w zasięgu funkcji
	* *************************************************************************
	* Użycie makra z biblioteki Boost jest również (a może nawet bardziej)
	* przydatne w zasięgu generycznych funkcji, do sprawdzania parametrów szablonu.
	* Poniżej przykład funkcji liczącej silnię, która akceptuje jedynie liczby
	* całkowite bez znaku (dla innych silnia jest niezdefiniowana):
*/

template<typename T> T factorial(T arg) {
	BOOST_STATIC_ASSERT(std::numeric_limits<T>::is_integer && !std::numeric_limits<T>::is_signed);
	
	T result = T(1);
	if (arg > T(0))
		while (arg > T(0)) {
			result *= arg;
			--arg;
		}
	return result;
}

/*
	* Uwaga do powyższego przykładu: Oczywiście możemy np. zaimplementować klasę, która
	* będzie "udawać" liczbę całkowitą bez znaku o nieograniczonej ilości bitów
	* (przeciążając odpowiednie operatory) i wyspecjalizować klasę std::numeric_limits,
	* by poprzez is_integer zwracała true, a przez is_signed false dla naszego nowego typu.
	* To pozwoli używać powyższej funkcji dla naszych "spreparowanych" liczb.
	*
	* *************************************************************************
	* BOOST_STATIC_ASSERT w zasięgu klasy
	* *************************************************************************
	* Makra BOOST_STATIC_ASSERT można używać również w zasięgu klasy. Najbardziej przydaje
	* się to znów w przypadku szablonów, gdyż pozwala upewnić się, że argument szablonu
	* spełnia stawiane przed nim wymagania, których inaczej nie dałoby się sprawdzić podczas
	* kompilacji. Poniżej znajduje się przykład klasy implementującej teksturę dla potrzeb
	* grafiki, gdzie użyto bardzo wydajnych algorytmów, które mają jednak to ograniczenie,
	* że działają tylko gdy rozmiary tekstury są potęgą liczby 2:
*/

template<unsigned int SIZE> class SquareOldStyleTexture {
	BOOST_STATIC_ASSERT((SIZE & (SIZE - 1)) == 0);
public:
	inline unsigned int getSize() { return SIZE; }
};

/*
	* **************************************************************************
	* Uwagi końcowe
	* **************************************************************************
	* Makro BOOST_STATIC_ASSERT pozwala wychwycić błędne (nizamierzone) użycia
	* szablonów, bądź mechanizmów zanim objawią się awarią działającej aplikacji.
	* Ma jednak przynajmniej dwie wady:
	* * nie można przekazać żadnego komunikatu dotyczącego błędu asercji.
	* * błąd asercji pojawia się w linii, w której użyto makra, a nie w linii
	*   w której występuje deklaracja, bądź wywołanie, które spowodowało ten błąd.
	* 
	* Powyższe dwie rzeczy sprawiają, że BOOST_STATIC_ASSERT jest dosyć niewygodny
	* w użyciu.
	*
	* **************************************************************************
	* Na koniec przykładowy program.
	* **************************************************************************
	* Poniższy program można skompilować z makrami BOOST_STATIC_ASSERT_ERROR2
	* oraz BOOST_STATIC_ASSERT_ERROR3, które spowodują błąd kompilacji pokazując
	* prawidłowe działanie makra BOOST_STATIC_ASSERT.
*/
int main() {

#ifdef BOOST_STATIC_ASSERT_ERROR2
	int n = 5; //funckja factorial nie skompiluje się dla typu int, który jest typem ze znakiem
#else
	unsigned long int n = 5; //tu jest OK
#endif

	std::cout << n << "! == " << factorial(n) << std::endl;

#ifdef BOOST_STATIC_ASSERT_ERROR3
	SquareOldStyleTexture<432> texture; //ta linia nie skompiluje się, gdyż 432 nie jest potęgą liczby 2.
#else
	SquareOldStyleTexture<512> texture; // tekstura o poprawnym rozmiarze
#endif

	std::cout << "Tekstura ma rozmiar " << texture.getSize() << " pikseli." << std::endl;

	return 0;
}
