// Piotr Dański	E1ISIII

//	Biblioteka variant służy do bezpiecznego przechowywania i pozyskiwania wartości z ograniczonego
//	zestawu typów. Umożliwia w jednym kontenerze przechowywanie wartości heterogenicznych (przykład na końcu pliku)
//	Umożliwia także prostą metodę wizytacji przechowywanych wartości.

// W tym pliku nagłówkowym jest zgromadzona cała biblioteka Variant
#include <boost/variant.hpp>

// Sama biblioteka została podzielona na części, które można dołączać wybiórczo zamiast całości
//	"boost/varian/variant_fwd.hpp"   - deklaracje zapowiadające szablonów klasy variant
//	"boost/varian/variant.hpp"       - definicje szablonów klasy variant
//	"boost/varian/apply_visitor.hpp" - wizitacja typów wariantowych
//	"boost/varian/get.hpp"           - szablon funkcji get
//	"boost/varian/variant_fwd.hpp"   - definicja szablonu klasy visitor

//	Potrzebne do napisania przykładu
#include <cstdlib>
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

//	Wizytator jest to klasa funkcyjna, która pozwala w bezpieczny sposób, ze względu
//	na typ, dokonać pewnej operacji na typie wariantowym w zależności od tego jaki
//	typ przechowuje
//	Przykładowy wizytator wypisuje na standardowe wyjście nazwę typu i jego wartość

class Wizytator : public boost::static_visitor<void>

//	Wizytator musi dziedziczyć po klasie boost::static_visitor, która udostępnia
//	definicję typu (result_type).
{
public:

//	zaleca się aby parametrami przeciążonych operatorów () wizytatora były referencje
//	ponieważ wtedy unikniemy kosztownego kopiowania obiektów, a jeżeli rozszerzymy zakres
//	typów zmiennej wariantowej nie będzie uruchamiana nieodpowiednia metoda przez niejawną 
//	konwersję typów ponieważ kod nie da się skompilować.
    void operator()(int& i) const
    {
        std::cout << "To int: " << i << std::endl;
    }

    void operator()(std::string& s) const
    {
        std::cout << "To std::string: " << s << std::endl;
    }

    void operator()(double& d) const
    {
        std::cout << "To double: " << d << std::endl;
    }
};

int main(int argc, char *argv[])
{
//	deklaracja typu wariantowego, w tym wypadku przechowującego int, std::string lub double
//	typy mogą być zarówno typami wbudowanymi, jak również dowolną klasą jeżeli przy
//	deklaracji nie poda się żadnego parametru, zmienna wariantowa będzie przechowywać
//	wartość pierwszego z wymienionych typów, dlatego musi on dawać się skonstrułować
//	w sposób domyślny. W tym przypadku przechowuje typ int
    boost::variant<int, std::string, double> example_variant;

    Wizytator wizytator;
    std::cout << "Wynik inicjalizacji bezparametrowej dla\nboost::variant<int, std::string, double>" << std::endl << std::endl;

//	funkcja boost::apply_visitor(wizytator, zmienna_wariantowa) pozwala na wizytację
//	zmiennej za pomocą określonego wizytatora
    boost::apply_visitor(wizytator, example_variant);

//	boost::variant posiada również konstruktor kopiujący, który tworzy egzemplarz zmiennej wariantowej
//	z kopią parametru, typ parametru musi dawać się jednoznacznie skonwertować na jeden z typów z zestawu
//	zmiennej wariantowej
    boost::variant<int, std::string, double> variant2("Napis");
    std::cout << std::endl << "Wynik inicjalizacji boost::variant<int, std::string, double>\nz parametrem \"Napis\"" << std::endl << std::endl;
    boost::apply_visitor(wizytator, variant2);

//	konstruktor pozwalający utworzyć egzemplarz variant na podstawie innego egzemplarza, taka operacja jest
//	możliwa, jeżeli każdy typ z zestawu typów z parametru daje się skonwertować na któryś z typów konstruowanych
    boost::variant<int, std::string, double> variant4(variant2);
    std::cout << std::endl << "Wynik inicjalizacji boost::variant<int, std::string, double>\nna podstawie innego egzemplarza" << std::endl << std::endl;
    boost::apply_visitor(wizytator, variant4);

//	poprawnie napisany wizytator nie obsłuży typu, którego nie ma jawnie opisanego
//	zabezpiecza to przed konwersją niejawną
//	aby sprawdzić, że rzeczywiście się nie skompiluje należy odkomentować poniższą linię
    boost::variant<int, std::string, double, char> variant3('a'); // zmienna wariantowa poszeżona o char
//	boost::apply_visitor(wizytator, variant3);

//	destruktor usuwa obiekt i wywołuje destruktor na rzecz przechowywanej wartości, jeżeli przechowuje wska?nik nic nie robi
    variant3.~variant();
    variant4.~variant();

//	operator przypisania zamienia bieżącą wartość nową. Typ parametru musi dawać się jednoznacznie skonwertować
//	na jeden z typów z zestawu zmiennej wariantowej. Jeżeli przechowywany typ jest identyczny z typem parametru
//	używany jest kopiujący operator przypisania, w innym przypadku używany jest konstruktor kopiujący, a bieżąca
//	wartość jest porzucana
    example_variant = 1.1;
    std::cout << std::endl << "Wynik operatora przypisania dla parametru 1.1" << std::endl << std::endl;
    boost::apply_visitor(wizytator, example_variant);
    std::cout << std::endl << "Wynik operatora przypisania dla kolejno \"Napis\", 2, 'a', 2.0" << std::endl << std::endl;
    example_variant = "Napis";
    boost::apply_visitor(wizytator, example_variant);
    example_variant = 2;
    boost::apply_visitor(wizytator, example_variant);
    example_variant = 'a'; // tu char został niejawnie skonwertowany do int ('a' = 97)
    boost::apply_visitor(wizytator, example_variant);
    example_variant = 2.0;
    boost::apply_visitor(wizytator, example_variant);


//	metoda which zwraca indeks (od zera) typu, którego wartość jest aktualnie przechowywana, według kolejności
//	zadeklarowanych typów
    std::cout << std::endl << "Wynik metody which() dla boost::variant<int, std::string, double>\nprzechowującej wartość typu double" << std::endl;
    std::cout << example_variant.which() << std::endl << std::endl;
    
//	metoda empty() zawsze zwraca false, jest wprowadzona tylko w celu zgodności z boost::any
    std::cout << "Wynik metody empty()" << std::endl;
    std::cout << example_variant.empty() << std::endl << std::endl;
    
//	metoda type() zwraca wartość type_info() dla przechowywanej aktualnie wartości
    std::cout << "Wynik metody type().name()" << std::endl;
    std::cout << example_variant.type().name() << std::endl << std::endl;

//	operator porównania zwraca true jedynie gdy zgodne są typy i wartości przechowywane w obu zmiennych wariantowych
    std::cout << "Wynik operatora = na dwóch zmiennych wariantowych przechowujących\n - różne typy\t\t\t\t";
    std::cout << (example_variant == variant2) << std::endl;
    example_variant = 2;
    variant2 = 3;
    std::cout << " - takie same typy, ale różne warości\t";
    std::cout << (example_variant == variant2) << std::endl;
    variant2 = 2;
    std::cout << " - takie same typy i warości\t\t";
    std::cout << (example_variant == variant2) << std::endl << std::endl;

//	analogicznie działa operator <, jeżeli typu wartości są różne zwraca which() < which(), dla obu zmiennych
//	jeżeli typy są identyczne zwraca wynik operatora < dla wartości.
    std::cout << "Wynik operatora < dla zmiennych wariantowych\nboost::variant<int, std::string, double> przechowujących\n - różne typy (pierwsza int, druga double)\t";
    example_variant = 2;
    variant2 = 3.0;
    std::cout << (example_variant < variant2) << std::endl;
    std::cout << " - różne typy (pierwsza double, druga int)\t";
    example_variant = 2.0;
    variant2 = 3;
    std::cout << (example_variant < variant2) << std::endl;
    std::cout << " - takie same typy, (pierwsza = 2, druga = 3)\t";
    example_variant = 2;
    variant2 = 3;
    std::cout << (example_variant < variant2) << std::endl;
    std::cout << " - takie same typy, (pierwsza = 3, druga = 2)\t";
    example_variant = 3;
    variant2 = 2;
    std::cout << (example_variant < variant2) << std::endl << std::endl;

//	w bardzo prostych przypadkach można zastosować metodę get,
//	metoda get<typename T>(boost::variant) zwraca wartość typu T, jeżeli zmienna wariantowa
//	przechowuje wartość typu T, w przeciwnym razie zrzuca wyjątek boost::bad_get
//	testowanie wszystkich możliwości tą metodą jest nieeleganckie i nieefektywne, w takim
//	celu stosuje się wizytatora
    std::cout << "Metoda get" << std::endl;
    example_variant = "Napis";
    try
    {
        double d = boost::get<double>(example_variant);
    }
    catch (boost::bad_get g)
    {
        std::cout << "Niezgodność typów (wyjątek)" << std::endl;
    }
    
//	jeżeli nie chcemy obsługiwać wyjątku można wywołać get ze wska?nikiem na zmienną wariantową
//	wtedy przy niezgodności typów zwrócony będzie wska?nik pusty
    double* d = boost::get<double>(&example_variant);
    if (d == NULL)
       std::cout << "Niezgodność typów (wska?nik)" << std::endl;

//	boost::variant pozawala m.in. na przechowywanie w jednym kontenerze heterogenicznych typów
    std::vector<boost::variant<int, std::string> > kontener;

//	pozwala to dodawać do kontenera elementy bez dbania o ich typ, o ile należą do zestawu zmiennej wariantowej
//	lub dają się jednoznacznie skonwertować na jeden z typów w zestawie
    kontener.push_back(2);
    kontener.push_back("Napis");
    kontener.push_back(1);
    kontener.push_back("Inny Napis");
    
//	dzięki odpowiedniemu zdefiniowaniu operatora < sortowanie ustawi wartości tego samego typu obok siebie
//	i oczywiście posortowane według wartości
    std::sort(kontener.begin(), kontener.end());
    
//	za pomocą wizytatora można bez dbania o typ wartości w kontenerze wykonywać operację na elementach kontenera
//	np. wypisać na standardowe wyjście
    std::cout << std::endl << "Wypisywanie zawartości kontenera" << std::endl;
    
    std::vector<boost::variant<int, std::string> >::iterator it;
    for (it = kontener.begin(); it != kontener.end(); ++it)
        boost::apply_visitor(wizytator, *it);
    
    return 0;
}
