10 lutego 2013

Funkcja lambda - python

Funkcja lambda jest funkcją, która pobiera dowolną liczbę argumentów i zwraca wartość, którą otrzymujemy po wykonaniu pojedynczego wyrażenia. Nie mogą one zawierać poleceń, ani więcej niż jednego wyrażenia. Stosuje się je tam, gdzie duża liczba zwykłych i bardzo prostych funkcji, po prostu zaśmiecałaby kod.
l = lambda a, b: a + b
print l(4, 5)

res = sorted([4, -5, 1, -2, -8, 7], cmp=lambda x, y: abs(y) - abs(x))
print res
Wynik:
9
[-8, 7, -5, 4, -2, 1]

9 lutego 2013

Generator expressions i generator function - Python

Generatory są szczególnie przydatne, gdy nie chcemy znać wszystkich wyników jakie może zwracać funkcja, a jedynie pierwszy, który będzie spełniać nasze kryteria. Generowanie kolejnych wyników, jest triggerowane przez nas.

Generatory expresion budujemy podobnie jak list comprehension (w tym celu korzystamy z nawiasów '()'). Aby wymusić wygenerowanie kolejnego wyniku korzystamy z metody bądź funkcji next(). Gdy generator nie jest w stanie już nic wygenerować, rzucany jest wyjątek "StopIteration".
gen_expression = (val for val in [1, 2, 3, 4, 7] if val % 2)

try:
    print gen_expression.next()
    print next(gen_expression)
    print gen_expression.next()
    print gen_expression.next()
except StopIteration:
    print 'Dostalem wyjatek'
Wynik:
1
3
7
Dostalem wyjatek
Drugim rodzajem generatora jest generator function, który wygląda jak zwykła funkcja. Pozwala jednak na korzystanie mechanizmu yield. W momencie, gdy metoda trafi na takie miejsce, jej działanie jest przerywane i zwracana jest kontrola do naszej części kodu.
def gen_fun(arg = 1):
    num = 0
    yield num
    while True:
        num += arg
        yield num
        yield -num

# Zwraca generator object
g = gen_fun()
print [g.next() for _ in xrange(15)]
Wynik:
[0, 1, -1, 2, -2, 3, -3, 4, -4, 5, -5, 6, -6, 7, -7]
Możemy również przekazać jakąś wartość do wnętrza generatora. W tym celu należy skorzystać z metody send(), które tak jak next() wznawia jego działanie. Jeżeli na takim generatorze zawołamy tylko next(), do środka przekazana zostanie wartość None.
def gen():
    arg = 1
    num = 0
    while True:
        num += arg
        a = yield num
        if a is not None:
            arg = a

g = gen()
print g.next()
print g.next()
print g.send(100)
print g.next()
Wynik:
1
2
102
202

8 lutego 2013

Range based for (C++11)

Chyba w odpowiedzi na całkiem udaną implementację BOOST_FOREACH (o której już pisałem) ficzer ten znalazł swoje miejsce w nowym standardzie. Znakomicie się on komponuje z wykorzystaniem słowa kluczowego auto niwelując pewne problemy (rozwijanie makr, np. w celu skorzystanie z iteratora do mapy trzeba było posłużyć się typedef) z którymi borykało się BOOST_FOREACH.
#include <iostream>
#include <string>

using namespace std;

int main()
{
    for (const auto& ch : string("rozciaganie"))
        cout << ch << " ";
    cout << endl;
    return 0;
}
Wynik:
r o z c i a g a n i e

7 lutego 2013

Detekcja typu (C++11) - auto i decltype

W nowym standardzie, możemy zlecić kompilatorowi wykrycie używanego przez nas typu. Służy do tego słowo kluczowe auto. Jest to szczególnie użyteczne dla typów zbudowanych przy pomocy szablów, które mogą być bardzo skomplikowane w czytaniu i zapisie. Uwaga! auto usuwa modyfikatory const i volatile, nowy wydedukowany typ nie będzie ich już posiadał.
#include <iostream>
#include <vector>

using namespace std;

int main()
{
    vector<int> v = {1, 2, 4, 6};
    auto vit = v.begin();
    cout << *vit << endl;
    return 0;
}
Drugim ficzerem jest detekcja typu przy pomocy decltype. Na podstawie zadanej zmiennej lub wyrażenia odgaduje typ, który będzie nadany nowej zmiennej. W przypadku wyrażenie, nie dochodzi do ewaluacji, wyciągany jest jedynie zwracany typ. Warto też pamiętać, że jeżeli wsadzimy do decltype zmienną i ujmiemy ją w dodatkowe nawiasy, typ który dostaniemy zawsze będzie referencją, a nowa zmienna będzie musiała być zainicjalizowana. Jeżeli natomiast z nawiasów nie skorzystamy to referencję uzyskamy tylko, gdy sama zmienna będzie typem referencyjnym.
#include <iostream>
#include <vector>
#include <list>
#include <boost/type_index.hpp>

using namespace std;

list<double>::iterator get_it(list<double> l)
{
    return l.begin();
}

int main()
{
    list<double> l = {7.1, 2.3, 4.6};
    decltype(get_it(l)) it = get_it(l); // list<double>::iterator it =  get_it(l);
    cout << boost::typeindex::type_id_with_cvr<decltype(it)>().pretty_name() << " " << *it << endl;

    int i = 879;
    decltype((i)) var1 = i;  // int& var1 = i;
    cout << boost::typeindex::type_id_with_cvr<decltype(var1)>().pretty_name() << " " << var1 << endl;

    int& j = i;
    decltype(j) var2 = i;    // int& var2 = i;
    cout << boost::typeindex::type_id_with_cvr<decltype(var2)>().pretty_name() << " " << var2 << endl;

    decltype(i) br = 54;     // int br = 54;
    cout << boost::typeindex::type_id_with_cvr<decltype(br)>().pretty_name() << " " << br << endl;

    return 0;
}
Wyniki:
std::_List_iterator<double> 7.1
int& 879
int& 879
int 54

5 lutego 2013

Deklaracja zmiennych i trochę o const/constexpr

Najpierw małe przypomnienie, jak wygląda deklaracja zmiennych w C++. Rzadko stosuję i spotykam deklaracje zmiennych jakiegoś typu po przecinku, stąd czytając książkę, sam byłem zaskoczony, co tak naprawdę znaczył ten kod.
int i, *ip = nullptr; // i jest typu int, ip jest wskaźnikiem na int.
int* p1, p2;          // p1 jest wskaźnikiem na int, p2 jest typu int.
int* a, &b = *ip;     // a jest wskaźnikiem na int, b jest referencją na wartość 
                      // wskazywaną przez ip.
A teraz trochę o const. Zawsze jest to dla mnie mylące, przynajmniej po dłuższym okresie czasu, gdy wiedza powolutku wyparowuje.
const int c = 8;  // c jest stałą typu int, jego wartość nie może być zmieniona.
const int &r = c; // ok, referencja na stały obiekt typu int - mówimy co będziemy mogli 
                  // robić przy  pomocy referencji - odwołując się do r nie damy rady 
                  // zmodyfikować c (chociaż sam w sobie nie jest const).
int &r2 = c;      // error, referencja na niestały obiekt.
Nie ma czegoś takiego jak stałe referencje (z technicznego punktu widzenia), są tylko referencje na stałe. Ponieważ nie można zmusić referencji by wskazywała na inny obiekt w jakimś sensie wszystkie referencje są stałe.
double dval = 3.14;
const double *pt = &dval; // ok (tak jak wyżej), ale nie możemy zmieniać dval, przy pomocy pt
A teraz co powstanie, gdy const znajdzie się w różnych miejscach? Pewne oznaczenia - cytując za książką:
Używamy terminu top-level const do oznaczenia, że sam wskaźnik jest stały. Kiedy wskaźnik może wskazywać na stały obiekt, odnosimy się do tego jako low-level const
int i = 0;
const int c1 = 88;        // c1 jest stałym obiektem typu int
int const c2 = 99;        // (TO SAMO) c2 jest stałym obiektem typu int

int *const p1 = &i;       // p1 jest stałym wskaźnikiem na obiekt typu int (top-level const).
const int ci = 42;        // ci jest stałym obiektem typu int (top-level const).
const int *p2 = &ci;      // p2 jest wskaźnikiem na stały obiekt typu int (low-level).
int const *p3 = &ci;      // (TO SAMO) p3 jest wskaźnikiem na stały obiekt typu int.
const int *const p4 = p2; // p3 jest stałym wskaźnikiem na stały obiekt typu int
                          // (prawy const jest top-level; lewy nie)
const int &r = i;         // referencja na stały obiekt typu int.
                          // const w typach referencyjnych zawsze jest low-level.
W odróżnieniu od referencji, wskaźniki są obiektami, więc mogą być const, co znaczy, że po ustawieniu w momencie inicjalizacji nie mogą na nic innego wskazywać.

A teraz o nowościach. const expression jest wyrażeniem, którego wartość nie może zostać zmieniona, i dla którego wartość powinna być (ale nie musi) obliczona podczas kompilacji. W C++11 pojawia się nowy modyfikator constexpr, który zbada, czy tak rzeczywiście się stanie. Funkcje z constexpr muszą być na tyle proste by kompilator poradził sobie z nimi podczas kompilacji, ale w kolejnych wersjach standardu konstrukcje będą stawać się coraz bardziej skomplikowane (pętle, algorytmy STL, kontenery itp.). W zamyśle constexpr ma stać się alternatywą dla metaprogramowania w szablonach.
#include <iostream>
#include <stdio.h>

using namespace std;

constexpr int get_size()
{
    return 6;
}

int main()
{
    constexpr int sz = get_size() + 80; // ok, pod warunkiem, że funkcja też jest constexpr.
    printf("%d", sz);
    return 0;
}
I podgląd tego co wyprodukował kompilator (g++ -std=c++17 -S -masm=intel main.cpp). Jak widać w pliku binarnym pojawia się już obliczona wartość (80+6=86).
main:
.LFB1824:
    .cfi_startproc
    endbr64
    push    rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    mov     rbp, rsp
    .cfi_def_cfa_register 6
    sub     rsp, 16
    mov     DWORD PTR -4[rbp], 86
    mov     esi, 86
    lea     rdi, .LC0[rip]
    mov     eax, 0
    call    printf@PLT
    mov     eax, 0
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc

29 grudnia 2012

include-what-you-use, pierwsze podejście

Na GoingNative 2012, Chandler Carruth wspominał o narzędziu (korzystającego z API clang-a), które ułatwiało by zarządzanie plikami nagłówkowymi, sugerując przeniesienie ich deklaracja do miejsc, gdzie są naprawdę potrzebne, co istotnie miało przyśpieszyć czas kompilacji. Nie udało mi się uzyskać informacji, gdzie to narzędzie można znaleźć. Pocztą pantoflową, dowiedziałem się o include-what-you-use.

Dodatkowe, przydatne linki:
http://clang.llvm.org/get_started.html
http://code.google.com/p/include-what-you-use/wiki/InstructionsForUsers

Niestety pierwsza, próba zakończyła się dla mnie porażką. Korzystając z wersji "trunk" obu projektów, kompilacja nie powiodła się. Postanowiłem więc skorzystać z odrobinę starszych wersji (RELEASE_30/final), dla obu projektów. Wydawało mi się, że tak zsynchronizowane projekty, już razem zadziałają. Miałem rację, przynajmniej jeśli chodzi o kompilację. Poniżej, sekwencja poleceń, która mi to zapewniła:
svn co http://llvm.org/svn/llvm-project/llvm/tags/RELEASE_30/final llvm

cd llvm/tools
svn co http://llvm.org/svn/llvm-project/cfe/tags/RELEASE_30/final clang
cd ../..

cd llvm/projects
svn co http://llvm.org/svn/llvm-project/llvm/tags/RELEASE_30/final/projects compiler-rt
cd ../..

cd llvm/tools/clang/tools
svn co http://include-what-you-use.googlecode.com/svn/tags/clang_3.0/ include-what-you-use
cd ..
# Wyedytować tools/clang/tools/Makefile i dodać include-what-you-use do zmiennej DIRS
# Wyedytować tools/clang/tools/CMakeLists.txt i dodać add_subdirectory(include-what-you-use)
cd ../../..

# Aby nie zaśmiecać, tworzymy katalog specjalnie na build (nie będzie make install)
mkdir build
cd build
../llvm/configure
make
Niestety, dla mojego testowego projektu, działanie programu kończy się błędem (co jest oczekiwane - nie wiem, tylko czy to ma być taki błąd), i nie generuje raportu.
make -k CXX=~/build/Debug/bin/include-what-you-use > log
Ku pamięci, mam nadzieje, że wrócę jeszcze kiedyś do tematu.

28 grudnia 2012

anonymous/unnamed namespace

Nienazwane namespace, mają zastąpić static globals. Ich głównym zadaniem jest ukrycie czegoś. Można się do nich dostać tylko w danej jednostce kompilacji, którą jest plik cpp. Każdy nienazwany namespace jest unikalny. Jednym z przykładów, może być chęć stworzenia “lokalnej” klasy w pliku cpp, ale ponieważ klasa o takiej nazwie już istnieje w projekcie, wystąpi błąd. Rozwiązaniem jest skorzystanie z tego mechanizmu.

Plik Second.hpp:
#ifndef SECOND_HPP
#define SECOND_HPP
#include <iostream>

using namespace std;

namespace bbb
{
    namespace
    {
        int b = 77;
    }

    class Klasa {
    public:
        void count2();
    };
}
#endif // SECOND_HPP
Plik Second.cpp:
#include <vector>
#include <boost/assign/list_of.hpp>
#include "Second.hpp"

namespace bbb
{
    namespace
    {
        int value = 2;

        bool isEven(int i) {
            return (i == value);
        }
    }

    void Klasa::count2() {
        vector<int> v = boost::assign::list_of(1)(2)(3)(2)(1)(1);
        cout << "call bbb::Klasa::count2() = " << count_if(begin(v), end(v), isEven) << endl;
    }
}
Plik main.cpp:
#include <iostream>
#include "Second.hpp"

using namespace std;

namespace std
{
    void moja() {
        cout << "call std::moja()" << endl;
    }
}

namespace aaa
{
    namespace
    {
        std::string a = "asdf";
        void access() {
            cout << "call aaa::anonymous::access()" << endl;
        }
    }

    void show() {
        cout << "call aaa::show()" << endl;
        access();
    }
}

int main()
{
    std::moja();
    aaa::access();
    aaa::show();

    bbb::Klasa s;
    s.count2();

//  bool l = bbb::isEven(4);    // ERROR!

    cout << "Access to member aaa::a = " << aaa::a << endl;
    cout << "Access to member bbb::b = " << bbb::b << endl;
//  cout << "Access to member bbb::value = " << bbb::value << endl;    // ERROR!

    return 0;
}
Wyniki:
call std::moja()
call aaa::anonymous::access()
call aaa::show()
call aaa::anonymous::access()
call bbb::Klasa::count2() = 2
Access to member aaa::a = asdf
Access to member bbb::b = 77