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

27 grudnia 2012

List Comprehensions

Nigdy, tego do końca nie rozumiałem, więc przyszła pora, żeby się w to zagłębić. Budowa tego cuda wygląda następująco:
result = [*transform* *iteration* *filter*]
Pierwsze wykonywana jest iteracja (jakaś pętla), jej wyniki podlegają filtrowaniu, to co przejdzie filtrowanie (opcjonalny mechanizm), może zostać jeszcze przetransformowane, a ostateczny wynik trafia jako element listy result.
Prosty przykład:
print [x for x in range(10)]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Do filtra trafiają wyniki iteracji, jest to konstrukcja "if", jeżeli zwróci True, wartość zostanie przekazana do transformowania. W przykładzie powyżej transformacji nie ma, wynik jest umieszczany jako element listy. Tutaj przykład z użyciem filtra.
def ftr(x):
    if x % 2 == 0:
        return True
    return None

print [x for x in range(10) if ftr(x)]
[0, 2, 4, 6, 8]
Wydaje mi się (nie udało mi znaleźć), że do konstrukcji filtra nie można używać "else" albo "elif", ale zastosowanie warunków logicznych wydaje się wystarczające.
print ['zero' if x == 0 else 'six' if x == 6 else str(x) + '?'
       for x in range(10) if x % 2 == 0 or x % 3 == 0]
['zero', '2?', '3?', '4?', 'six', '8?', '9?']
W przypadku transformacji, możemy skorzystać z instrukcji warunkowej "else". Nie można skorzystać z "elif", ale bez tego można sobie poradzić. Poniżej ekwiwalent transformacji, jaka została użyta, w konstrukcji listy.
def tsfm(x):
    if x == 0:
        return 'zero'
    else:
        if x == 6:
            return 'six'
        else:
            return str(x) + '?'

26 grudnia 2012

C++11/17 - inicjalizacja zmiennych

Nowy standard dostarcza nowy sposób inicjalizacji zmiennych (można nareszcie wygodnie inicjalizować kontenery). Co ciekawe, w przypadku typów prostych i konwersji, która może doprowadzić do utraty danych kompilator wyświetli ostrzeżenie.
#include <iostream>

using namespace std;

int main()
{
    long double ld = 3.1415926536;
    int curly_from_ld_1{ld};    // warning and data lost
    int curly_from_ld_2 = {ld}; // warning and data lost
    cout << ld << " " << curly_from_ld_1 << " " << curly_from_ld_2 << endl;

    int round_from_ld_1(ld);    // no-warning, data lost
    int round_from_ld_2 = ld;   // no-warning, data lost
    cout << ld << " " << round_from_ld_1 << " " << round_from_ld_2 << endl;

    long long ll = 5000000000;
    int curly_from_ll_1{ll};    // warning and data lost
    int curly_from_ll_2 = {ll}; // warning and data lost
    cout << ll << " " << curly_from_ll_1 << " " << curly_from_ll_2 << endl;

    int i = 56;
    long long curly_from_int{i};
    cout << curly_from_int << endl;

    return 0;
}

Testowane z g++ (Ubuntu 9.2.1-9ubuntu2) 9.2.1 20191008,
$ g++ -std=c++17 main.cpp 

main.cpp: In function ‘int main()’:
main.cpp:8:25: warning: narrowing conversion of ‘ld’ from ‘long double’ to ‘int’ [-Wnarrowing]
    8 |     int curly_from_ld_1{ld};    // warning and data lost
      |                         ^~
main.cpp:9:28: warning: narrowing conversion of ‘ld’ from ‘long double’ to ‘int’ [-Wnarrowing]
    9 |     int curly_from_ld_2 = {ld}; // warning and data lost
      |                            ^~
main.cpp:17:25: warning: narrowing conversion of ‘ll’ from ‘long long int’ to ‘int’ [-Wnarrowing]
   17 |     int curly_from_ll_1{ll};    // warning and data lost
      |                         ^~
main.cpp:18:28: warning: narrowing conversion of ‘ll’ from ‘long long int’ to ‘int’ [-Wnarrowing]
   18 |     int curly_from_ll_2 = {ll}; // warning and data lost
      |                            
Wynik:
3.14159 3 3
3.14159 3 3
5000000000 705032704 705032704
56

Uniform initialization

W C++17 prawie się udało wprowadzić ujednolicony sposób inicjalizacji. Ciągle jeszcze są pewne problemy (linijka 14, a także coś z atomics i agregatami), ale zdaje się że zostaną one naprawione w przyszłych wersjach standardu. W każdym razie programiści są zachęcani, by wszędzie tam gdzie to możliwe stosować klamerki w celu inicjalizacji [1].
#include <iostream>
#include <boost/type_index.hpp>

using namespace std;

int main()
{
    auto var1{17};          // std::initializer_list<int> w C++11, w C++17 int
//  auto var2{1, 3, 6};     // std::initializer_list<int> w C++11, w C++17 ERROR

    cout << boost::typeindex::type_id_with_cvr<decltype(var1)>().pretty_name() << endl;
//  cout << boost::typeindex::type_id_with_cvr<decltype(var2)>().pretty_name();

    auto var3 = {1, 3, 6};  // Ups nieintuicyjne, nadal std::initializer_list<int>

    cout << boost::typeindex::type_id_with_cvr<decltype(var3)>().pretty_name() << endl;
}
Wynik:
int
std::initializer_list<int>
Inne ciekawostki, to inicjalizacja FDT (fundamental data types) domyślną wartością (0, false, nullptr), gdy nawiasy klamerkowe będą puste. Struktury (także dziedziczące), możemy inicjalizować za pomocą zagnieżdżonych nawiasów, ale dodano także wygodne odwoływanie się do nazw pól:
#include <iostream>

using namespace std;

struct Base {
    int age;
    double high;
};

struct Child : public Base {
    string fun() { return name; };
    string name;
};

int main()
{
    int val{};
    cout << "FDT (fundamental data type): " << val << endl;

    Child child1{};     // czyli to samo co child1{0, 0, ""};
    cout << child1.age << " " << child1.high << " " << child1.name << endl;

    Child child2{{.age=1, 2}, .name="Tom"};
    cout << child2.age << " " << child2.high << " " << child2.name << endl;
}
Wynik:
FDT (fundamental data type): 0
0 0 
1 2 Tom

23 grudnia 2012

Prosty Makefile

Musiałem zbudować, bardzo mały plik Makefile. Tutaj przydatny tutorial:
http://www.cs.colby.edu/maxwell/courses/tutorials/maketutor/

Make bez argumentów uruchomi, pierwszą napotkaną regułę, w tym przypadku będzie to main. Po dwukropku możemy podać listę (oddzieloną spacjami) zależność np. nazwy reguł na zbudowanie plików *.o, zanim, będziemy z nich korzystać podczas linkowania. Komendy, które składają się na daną regułę muszą być poprzedzone tabulatorem!
CXX=g++

main:
        $(CXX) -o main main.cc Lion.cc Lungs.cc -I.

Istnieje możliwość przekazania parametrów, do skryptu, w poniższym przykładzie, zmieniamy kompilator z domyślnego gcc, na clang-a.
$ make CXX=clang++
clang++ -o main main.cc Lion.cc Lungs.cc -I.