﻿/********************************************************************
    created:    2008/04/13
    filename:     parameter.cpp
    file ext:    cpp
    requires:    Boost 1.34_1 or higher
    author:        Jakub Księżniak
    
    purpose:    Plik zawiera przykłady użycia biblioteki Boost::Parameter.
    Biblioteka ta pozwala przełamać bariery związane z parametrami funkcji.
    Otóż obecnie znamy taki sposób wywoływania funkcji:
      CreateWindow( "MyClass", "MyWindow", WS_POPUP, 100, 100, 400, 300,
                    NULL, lpMyMenu, hInstance, NULL);
    
    Taka funkcja jest niewygodna w użyciu, a w dodatku większość argumentów
    mogła by być domyślna. Czy zatem nie było by lepiej gdyby wywołanie takiej
    funkcji wyglądało tak:
      CreateWindow( "MyClass", "MyWindow", _width = 400, _hMenu = lpMyMenu);

    Tutaj właśnie wkracza biblioteka Boost::Parameter, która pozwala
    ominąć wszystkie ograniczenia związane z podawniem argumentów do funkcji.

    Zaczynamy.
*********************************************************************/

/**
 * Dodajemy kilka znanych nagłówków
 */
#include <iostream>
#include <ctime>
#include <string>
#include <boost/parameter.hpp> // i jeden najważniejszy.

using namespace std;

/**
 * Pierwszy przykład...
 *
 * Utworzymy małą biblioteczkę, która ułatwi nam formatowanie daty i czasu.
 * Dzięki niej zapoznamy się z podstawami działania boost::parameter.
 *
 * Napiszemy więc prostą funkcję, która wykorzysta funkcję strftime() z <ctime>
 * do formatowania tekstów.
 * Opis funkcji jest pod adresem:
 * http://cplusplus.com/reference/clibrary/ctime/strftime.html
 *
 * Przykładowe wywołanie naszej nowej funkcji może wyglądać tak:
 * char str[128];
 * timetostr( str, sizeof(str), _format = "%A");
 */
namespace timefmt
{
  /**
     * Na początek definiujemy słowa kluczowe, które będą wykorzystane
     * jako parametry funkcji. Są to makra, zatem nie stawiamy średników
     * na końcu linii.
     * Rozwinięta wersja tego makra wygląda tak:
     *
     * BOOST_PARAMETER_NAME((tag-name, namespace-name) object-name)
     * tag-name - nazwa struktury która będzie wskazywać nasz parametr
     * namespace-name - przestrzeń nazw, w której ma się znaleźć parametr
     * object-name - nazwa obiektu do którego możemy się odwoływac (dokładniej
     * const referencja do obiektu który podamy jako parametr)
     *
     * My jednak skorzystamy z wersji z jednym argumentem:
     * BOOST_PARAMETER_NAME(nazwa) // makro rozwija się na:
     *                             // BOOST_PARAMETER_NAME((nazwa, tag) _nazwa)
     */
    BOOST_PARAMETER_NAME(strptr) // należy zauważyć, że nie ma zdefiniownych typów
    BOOST_PARAMETER_NAME(maxsize)
    BOOST_PARAMETER_NAME(format)
    BOOST_PARAMETER_NAME(time_raw)
    BOOST_PARAMETER_NAME(local_func)

    /**
     * Deklarujemy funkcję za pomocą kolejnego makra, BOOST_PARAMETER_FUNCTION()
     * Jest to zbyt skomplikowane makro by tutaj, je opisywać, dlatego
     * odsyłam zainteresowanych pod adres: 
     * http://www.boost.org/doc/libs/1_34_1/libs/parameter/doc/html/reference.html#boost-parameter-function-result-name-tag-namespace-arguments
     *
     * Tutaj natomiast na kilku przykładach spróbuję pokazać możliwości
     * parametryzacji funkcji.
     * Niżej napiszemy prostą funkcję która będzie odpowiednikiem zwykłej funkcji:
     *   size_t simple_timetostr(char *strptr, size_t maxsize);
     */
     BOOST_PARAMETER_FUNCTION(
     (size_t),                   // 1. typ zwracany przez funkcję, musi być w nawiasach
     simple_timetostr,           // 2. nazwa funkcji

     tag,                        // 3. przestrzeń nazw w której znajdują się wcześniej zdefiniowane parametry
     (required                   // 4. słowo kluczowe oznacza, że parametry te są wymagane
         (in_out(strptr),        // 5. in_out() oznacza, że zmienna może być modyfikowana
         *)                      // 6. '*' oznacza, że strptr może być dowolnego typu
         (maxsize,   (size_t))   // 7. kolejny argument wymagany przez funkcję, tym razem jest typu size_t.
     )
     )  // brak średnika !
    {
        time_t curr_time = time(NULL);
        return strftime( (char*)strptr, maxsize, "%c", localtime(&curr_time) );
    }

    /**
     * Kolejna funkcja, różni się od poprzedniej tym, że pozwala na użycie
     * parametrów opcjonalnych. Na to w końcu czekaliśmy. :)
     */
    BOOST_PARAMETER_FUNCTION(
        (size_t),                // 1. ppoczątek taki jak poprzednio
        opt_timetostr,           // 2. nieco zmieniona nazwa
        tag,
        (required                 // 3. parametry wymagane przez funkcję
            (in_out(strptr),       *) 
            (maxsize,   (size_t))
        )
        (optional              // 3 parametry opcjonalne z wartościami domyślnymi
            (format,       (char const*), "%c"  ) // format dla zwracanego tekstu
            (time_raw,     (time_t), time(NULL) ) // czas podawany w sekundach
            (local_func,  *, localtime )          // wskaźnik na funkcję konwertującą
                                                  // obecny czas, domyślnie lokalny
        )
    ) // pamiętaj! brak średnika!
    {
        return strftime( (char*)strptr, maxsize, format, local_func(&time_raw) );
    }

   /**
    * Teraz przedstawiam wersję ostateczną, która różni się opcją 'deduced'
    * która pozwala na rozpoznanie parametrów po ich typach.
    */
    BOOST_PARAMETER_FUNCTION(
        (size_t), timetostr, tag,
        (required
            (in_out(strptr),       *) 
            (maxsize,   (size_t))
        )
        (deduced // opcję tą można użyć tylko w przypadku gdy poprzednio zdefinowano już sekcję (required...) lub (optional...)
               (optional
                (format,       (char const*), "%c"  )
                (time_raw,     (time_t), time(NULL) )
                (local_func,  *, localtime )
            )
        )
        )
    {
        return strftime( (char*)strptr, maxsize, format, local_func(&time_raw) );
    }
}


// Tutaj znajdują się przykłady użycia naszej biblioteki.
void test_timefmt()
{
    char strTime[128];
    time_t tt = time(NULL);

    cout << "Time Format test...\n";

    /**
     * Proste wywołanie timetostr() z wymaganymi parametrami, bez opcji.
     */
    timefmt::timetostr( strTime, sizeof(strTime));
    cout << "System time: " << strTime << endl;

    /**
     * Następnie są wywołania tej samej funkcji z użyciem opcji. Przy czym
     * na początku trzeba podać parametry wymagane przez funkcję,
     * a potem można podawać opcje w dowolnej kolejności.
     *
     * Uwaga! Nazwy parametrów są poprzedzane przez podkreślnik_ .
     */
    timefmt::timetostr( strTime, sizeof(strTime), 
                        timefmt::_format = "Today is %A. The %j day of the year %Y.");
    cout << strTime << endl;

    timefmt::timetostr( strTime, sizeof(strTime),
                        timefmt::_local_func = gmtime,
                        timefmt::_format = "Global timezone is %Z" );
    cout << strTime << endl;

    /**
     * Tutaj można zobaczyć działanie funkcji z dedukcją parametrów.
     * Dzięki temu wywołanie nie różni się niczym od zwykłej funkcji,
     * poza dowolną kolejnością podawanych parametrów.
     */
    timefmt::timetostr( strTime, sizeof(strTime), gmtime, 0,
                        "Time base: %d %B %Y.");
    cout << strTime << endl;

    timefmt::timetostr( strTime, sizeof(strTime), gmtime,
                        timefmt::_format = "Czas zimowy: %X", tt+3600);
    cout << strTime << endl;
}

/************************************************************************/
/* Teraz, gdy już potrafimy pisać funkcje z parametrami możemy zająć    */
/* się klasami. W tym przypadku idea pozostaje ta sama, jedyną róznicą  */
/* jest metoda definiowania konstruktora z parametrami.                 */
/************************************************************************/

/**
 * Deklarujemy parametry używane przez naszą klasę. Należy zauważyć, że
 * znajdują się poza klasą, ponieważ są umieszczone we własnej przestrzeni,
 * a co za tym idzie, nie mogą się znaleźć w ciele klasy.
 */
BOOST_PARAMETER_NAME(name)
BOOST_PARAMETER_NAME(hasJob)
BOOST_PARAMETER_NAME(child)
BOOST_PARAMETER_NAME(age)

/**
 * Na początek klasa pomocnicza, w której możemy zapisać implementację
 * naszego konstruktora parametrycznego. Wynika to bezpośrednio z ograniczeń
 * jakie nakłada na nas C++, ponieważ brakuje możliwości delegacji konstruktora.
 * Szczegóły znajdują się tutaj:
 * http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n1986.pdf
 * Dlatego stosujemy tutaj rozwiązanie z dziedziczeniem klasy bazowej, które
 * pozwoli nam w klasie pochodnej zastosować konstruktor parametryczny.
 */
class PersonData_impl
{
    // przykładowe zmienne prywatne
    string            name;     // imie osoby
    bool            hasJob;     // czy osoba ma pracę
    PersonData_impl *child;     // dziecko osoby, jeśli jakieś ma
    unsigned        age;        // wiek osoby

public:
    /**
     * Nasz konstruktor 'domyślny', posiada tylko jeden parametr.
     * ArgumentPack, jest klasą która przechowuje referencje do wszystkich argumentów
     * podawanych przy wywołaniu.
     */
    template <class ArgumentPack>
    PersonData_impl(ArgumentPack const& args)
    {
        // Inicjalizujemy zmienne
        name = args[_name]; // pobieramy argument z parametru 'name'.
        hasJob = args[_hasJob | false]; // Jeżeli parametr jest opcjonalny, 
                                        // to użytkownik nie musi go podawać. W tym wypadku
                                        // musimy ręcznie zdefiniować wartość domyślną,
                                        // która jest łączona za pomocą operatora |.
        child = reinterpret_cast<PersonData_impl*>( args[_child | 0L] );
        age = args[_age | 0];
    }

    // kilka metod stworzonych na potrzeby przykładu
    string GetName() const { return name; };
    void SetAge(const unsigned &a) { age = a; };
    unsigned GetAge() const { return age; };
    void SetJob(const bool &job) { hasJob = job; };
    bool HasJob() const { return hasJob; };
    bool HasChild() const { return child != NULL; };
    void SetChild(PersonData_impl* c) { child = c; };
    PersonData_impl* GetChild() { return child; };
};

/**
 * Zatem przechodzimy do klasy właściwej.
 */
class Person : public PersonData_impl
{
public:
    /**
     * Definiujemy konstruktor za pomocą nowego makra, które nieznacznie różni
     * się od znanego już BOOST_PARAMETER_FUNCTION().
     */
    BOOST_PARAMETER_CONSTRUCTOR(
        Person,                         // 1. Nazwa naszej klasy
        (PersonData_impl),              // 2. Nazwa klasy bazowej, musi być w nawiasach
        tag,
        (required (name, (string)))
        (optional                       // Tutaj już nie wpisujemy wartości domyślnych,
            (hasJob, (bool))            // zrobiliśmy to w konstruktorze klasy bazowej.
            (child,  (PersonData_impl*))
        )) // Po tym makrze nie ma już ciała konstruktora. To kolejna różnica.

    /**
     * Prosty operator pozwoli nam przetestować działanie klasy.
     */
    friend ostream& operator<<(ostream &os, Person &p)
    {
        os << "Name: " << p.GetName() << "\thasJob: " << p.HasJob() << "\tAge: ";
        os << p.GetAge() << "\tChild: ";
        if(p.HasChild())
            os <<  p.GetChild()->GetName();
        else os << "none";
        return os << endl;
    };

    /**
     * Przykład metody parametrycznej, która pozwala ustawiać zmienne w klasie.
     * Jest to swego rodzaju uniwersalny setter.
     */
    BOOST_PARAMETER_MEMBER_FUNCTION(
        (void), Set, tag,
        (optional                             // W przypadku nie podania parametru
             (hasJob,  (bool), this->HasJob()) // wartością domyślną jest wartość obecna
            (age,    (unsigned), this->GetAge()) // Pamiętajmy, że są to referencje!
            (child,  (PersonData_impl*), this->GetChild())
            )
        )
    {
        this->SetJob(hasJob);
        this->SetChild(child);
        this->SetAge(age);
    };

    /**
     * Teraz już wiadomo wszystko o wykorzystywaniu parametrów w funkcjach.
     * Zainteresowanych zapraszam na stronę:
     * http://www.boost.org/doc/libs/1_34_1/libs/parameter/doc/html/index.html#
     *
     * Znajdują się na niej różne mniej lub bardziej zaawansowane przykłady, jednak
     * te które tutaj przedstawiłem wystarczają, aby zacząć samemu tworzyć niezwykłe
     * biblioteki.
     */

    /**
     * Możemy teraz wykorzystać naszą wiedzę w praktyce. Zastanówmy się jakie możliwości
     * daje nam tak naprawdę boost::parameter.
     * 1. Ogromna elastyczność funkcji.
     * 2. Konstruktory z licznymi parametrami nie muszą być już uciążliwe.
     * 3. Settery w których za jednym razem ustawiamy te właściwości obiektu jakie chcemy
     * 4. i wiele innych...
     *
     * Pomyślmy, skoro mamy tyle możliwości, to dlaczego by nie użyć operatorów
     * parametrycznych? Otóż to. Można.
     * Należy w takim wypadku zastosowac trik znany już z przykładu z klasą bazową
     * i zastosowac klasę ArgumentPack.
     */
    template <class ArgumentPack>
    Person& operator=(ArgumentPack const& args)
    {
        SetJob( args[_hasJob | HasJob()] );
        SetAge( args[_age | GetAge()] );
        SetChild( args[_child | GetChild()] );
        return *this;
    }
    /**
     * Użyłem tutaj dość potężny operator przypisania. Jego przeciążenie
     * sprawia, że w niezwykły sposób możemy podawać kilka argumentów na raz
     * do naszego obiektu. Jest to odpowiednik metody Set(), tylko użyty w
     * specyficzny sposób.
     */

    /**
     * To nie koniec sztuczek. Skoro możemy za pomocą jednego przypisania
     * ustawiać wiele właściwości, to dlaczego by nie pobierać wielu wartości
     * za pomocą jednego operatora. To również jest możliwe!
     */
    template <class ArgumentPack>
    friend Person& operator>>(Person &p, ArgumentPack const& args)
    {
        // musimy utworzyć wskaźniki które mają zastąpić parametry, których
        // nie poda użytkownik przy wywołaniu.
        string *no_name = NULL;
        bool *no_hasJob = NULL;
        unsigned *no_age = NULL;
        PersonData_impl **no_child = NULL;

        // musimy teraz sprawdzić czy użytkownik podał konkretny parametr
        // jeżeli tak, to przypisz do niego wartość.
         if( args[_name | 0] )
             *args[_name | no_name] = p.GetName();
         if( args[_hasJob | 0] )
             *args[_hasJob | no_hasJob] = p.HasJob();
          if( args[_child | 0] )
              *args[_child | no_child] = p.GetChild();
         if( args[_age | 0] )
             *args[_age | no_age] = p.GetAge();
        return p;
    }

};

/**
 * Funkcja testująca klasę Person.
 */
void test_person()
{
    cout << "\nTest class with parameters...\n";
    //Person a();                   // Nie definiowaliśmy konstruktora domyślnego.
    Person b("Sue");                // Wymagany jest jeden argument 'name'
    Person c("Jack", _child=&b, _hasJob=false); // ale dostępne są też inne
    Person d("Kate", false, &b);                // mozemy podawać również w tradycyjny sposób
    cout << b << c << d;

    /**
     * Dokonujemy zmian w obiekcie c.
     */
    c.Set( _age = 42, _hasJob = true);

    // Sprawdzamy wprowadzone zmiany
    cout << "\nUse of Set() method with parameters. (Change: age, job)\n" << c;

    /**
     * Przypisujemu do obiektu... właśnie, wygląda to tak jakby ustawianie
     * nowych wartości dla obiektu d.
     */
    d = (_age = 32, _child = (Person*)NULL, _hasJob = true );

    // Sprawdzamy...
    cout << "\nExample of operator= with parameters. (Change: age, child, job)\n"
         << d << endl;

    string c_name;
    unsigned c_age = 0;
    bool c_job = false;
    PersonData_impl *c_child = NULL;

    /**
     * Teraz spróbujemy przypisać do zmiennych zadeklarowanych wyżej wartości
     * obiektu c.
     */
    c >> (_hasJob = &c_job, _age = &c_age, _child = &c_child, _name = &c_name);

    // Sprawdzamy...
    cout << c_name << " is " << c_age << " years old. ";
    cout << (c_job == true ? "He has a job." : "He doesn't have a job.");
    if (c_child)
    {
        cout << " He has one child, " << c_child->GetName() << ".";
    }
    cout << endl;
}

// punkt startowy
int main(int argc, char* argv[])
{
    test_timefmt();
    test_person();

    system("pause");
    return 0;
}
