• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

Szybkie wyzwanko C++

Object Storage Arubacloud
+1 głos
804 wizyt
pytanie zadane 30 marca 2016 w C i C++ przez 0xf Dyskutant (8,180 p.)

Czesc, mam pewna rozkmine zaczne od kodu i napisze o co chodzi.

#include <iostream>

using namespace std;



template <typename T> void print(const T& t) {
    cout << t << endl;
}

template <typename First, typename... Rest> void print(const First& first, const Rest&... rest)
{
    cout << first << ", ";        
    print(rest...); // recursive call using pack expansion syntax
}

int main()
{
print("first", 2, "third", 3.14159);
}

Dobra wyzwanie jest takie zeby funkcje print zamknac ze tak powiem w jednej funkcji czyli mamy jedna pare takich nawiasikow {} na te funkcje. 

A teraz male pytanie dlaczego to dziala ?

4 odpowiedzi

+4 głosów
odpowiedź 30 marca 2016 przez MetGang Nałogowiec (34,360 p.)

Otóż żebyś się nie zdziwił :D Istnieje możliwość zamknięcia tego w jednej funkcji:

template <typename Arg, typename... Args>
void doPrint(std::ostream& out, Arg&& arg, Args&&... args)
{
    out << std::forward<Arg>(arg);
    using expander = int[];
    (void)expander{0, (void(out << ',' << std::forward<Args>(args)),0)...};
}

Działa to na zasadzie ekspandera, który wpakowuje zera do int[] za pomocą listy inicjalizującej z parameter packiem ( int x[] = { args... }; ) i jednocześnie wywołuje wypisywanie (void jest po to zapewne żeby nie tworzyć de facto tej tablicy). Zastosowane są tu nawiasy () i ich zależność, że tylko ostatni argument jest brany pod uwagę ( int x = (0,1); x jest równe 1 ), a reszta się normalnie wykonuje. Przesyłanie std::ostream jest tu opcjonalne. Równie dobrze można samemu "statycznie" dać std::cout.

Jest to już wyższy poziom, ale mogę to jakoś jeszcze uprościć.

I ogólnie to jest piękne w C++, że można robić naprawdę dziwaczne rzeczy.

komentarz 30 marca 2016 przez 0xf Dyskutant (8,180 p.)
Jako ze jestem nowociota jezeli chodzi o swiat C++ to sie spytam czym expander i czy to cos int[]; ma cos wspolnego z tablicami czy moze jest to lambda ?

No i ta linia jest dla mnie czarna magia

(void)expander{0, (void(out << ',' << std::forward<Args>(args)),0)...};

}

Pozdrawiam
komentarz 30 marca 2016 przez MetGang Nałogowiec (34,360 p.)

Postaram się jak najjaśniej.

using Expander = int[];

Jest to alias (nazwa zastępcza) tablicy potrzebny tylko, aby kompilator nie gubił się przy tych wszystkich nawiasach i odpowiednio zinterpretował wyrażenie.

(void)Expander{ /* kod */ };

Jest to równoważne do (z tą różnicą, że nie tworzysz zmiennej):

int x[] = { /* kod */ };

Następnie mamy:

{ 0 , ( void(/* wypisanie */) , 0 ) };

Pierwsze zero jest po to żeby pominąć pierwsze wypisanie (wcześniej już coś wypisaliśmy), a pominęliśmy je, aby liczba argumentów w {} była równa przynajmniej 2, aby kompilator skapnął się, że ma do czynienia z listą inicjalizującą. Teraz zwykłe nawiasy (). Jak pisałem wcześniej działa to na zadzie, że tylko ostatni argument w () jest rozpatrywany. Dlatego 0 jest przypisywanie do int[] (Expandera), a poprzednie argumenty się wykonują (czyli następuje wypisywanie). void jest dodatkowo po to, aby nie tworzyć tymczasowej zmiennej std::ostream.

std::forward<Args>(args)

A to jest po prostu perfect forwading, który zapewnia, że argumenty nie będą kopiowane tylko "przekazane" do wypisania.

I teraz te trzy kropki ... Jest to rozpakowanie parameter packa, czyli powiedzenie kompilatorowi, aby N razy powtórzył dane wyrażenie, czyli wstawił po przecinku kolejne argumenty parameter packa.

template <typename... Ts> void Foo(Ts&&... Args)
{
    Bar(std::forward<Ts>(Args)...);
}
Foo(1,'R');

// dla kompilatora wygląda tak
template <typename T1, typename T2> void Foo(T1&& Arg1, T2&& Arg2)
{
    Bar(std::forward<T1>(Arg1),(std::forward<T2>(Arg2));
}
Foo(1,'R');

Ogólnie jest to skomplikowane, lecz dobre rozwiązanie. Szczerze powiedziawszy to przed napisaniem pierwszego posta mnie oświeciło jak to działa :p Coś jeszcze?

komentarz 30 marca 2016 przez 0xf Dyskutant (8,180 p.)
Dobra wielkie dzieki za rozbudowana odpowiedz, Czy cos jeszcze hehehe. Jak ogarne to co powyzej i przerobie w praktyce to moze wtedy dodam do swojego pytania cos jeszcze. Widze ze znasz C++ na tyle dobrze ze kombinujesz jak tu oszukac kompilator hehe fajna sprawa. Ogarne i sie odezwe. jeszcze raz dzieki :)
komentarz 30 marca 2016 przez criss Mędrzec (172,590 p.)
Cóż... Działa.

Niestety niewiele rozumiem z tego kodu.. Nie rozumiem sensu rzutowania na void. Nie wiem też co robi to 0 razem voidem jednym nawiasie oddzielone przecinkami (no ok, ostatni argument jest barny pod uwage, ale dlaczego wypisywanie nie może być zwyczajne w osobnej linii). Ledwo ogarniam w jakim celu używa się std::forward. Nie rozumiem też dlaczego początkowym elementem tablicy ma być 0. I właściwie w ogóle po co ta tablica XD Także tak...

Jak rozumiem z twoich przypuszczań, to nie jest twój kod? Mógłbyś zalinkować?
komentarz 30 marca 2016 przez MetGang Nałogowiec (34,360 p.)
No nie będę ukrywał, że jest to właśnie kod ze stackoverflow. Oszukaniem kompilatora bym tego nie nazwał, raczej sprytnym wykorzystanie możliwości C++. Ale ogólnie interesuję się takimi bardziej zaawansowanymi zagadnieniami ;)
komentarz 30 marca 2016 przez MetGang Nałogowiec (34,360 p.)
http://stackoverflow.com/questions/27375089/what-is-the-easiest-way-to-print-a-variadic-parameter-pack-using-stdostream

Tutaj link do oryginału - niestety bez żadnych wyjaśnień, więc moje musiałoby Ci wystarczyć. Być może jutro/pojutrze zrobię jakiś temat na forum z dokładnym wyjaśnieniem. Rzutowanie na void jest ogólnie po to by kompilator nie tworzył zmiennych tymczasowych tylko wykonał to co ma wykonać.
komentarz 30 marca 2016 przez criss Mędrzec (172,590 p.)
Po prostu komentarz napisałem bez uprzedniego refresha i nie widziałem twojego wytłumaczenia. Wiele wyjaśnia - dzięki. I dzięki za link.

Więc rzutowanie na void zapewnia, że żadna zmienna nie będzie utworzona, żadna pamięć nie zostanie zapisana?
komentarz 30 marca 2016 przez MetGang Nałogowiec (34,360 p.)
Na 100% pewien nie jestem, ale np. takie (void)int{0} lub (void)int(0) wyrażenie nie wywołuje żadnego błędu lub ostrzeżenia ze strony kompilatora. To tak jakbyś tworzył zmienną i rzutował ją na pustką, na nic. Nie wiem jak to kompilator potraktuje, lecz raczej nie bez powodu zostało to użyte w kodzie. Zapewne jakoś to optymalizuje na swój sposób.
+2 głosów
odpowiedź 30 marca 2016 przez criss Mędrzec (172,590 p.)
Jesli chodzi ci o zamknięcie w jednej funkcji, ale nadal wykorzystując parameter pack (pewnie o to ci chodzi, bo inaczej raczej nie da się osiągnąć dowolnej ilości argumentów), to nie da się tego zrobić. Pierwsza funkcja print robi za taki przypadek podstawowy w rekurencji odbywającej się w drugiej funkcji print. No a jak wiadomo bez przypadku podstawowego (kończącego rekurencje) w tym wypadku będzie to po prostu crash.

Podejrzewam, że ty też nie wiesz jak to zrobić, bo szukając możliwości wpadłem na dokładnie ten sam kod na MSDN :P Ja wysnułem tezę, że się nie da i myślę, że mam racje.
komentarz 30 marca 2016 przez 0xf Dyskutant (8,180 p.)
Owszem masz racje ale problem polega na tym ze kiedys szukajac zauwazylem ze jakiemus geniuszowi ze stacka sie udalo. Zgadzam sie nie ogarniam tych parameter pack'ow chcialbym jakos zamknac to w jednej funkcji bo parameter pack'i chce wykorzystac jako metody klasy a 2 metody klasy ktore sa tylko po to by jedna mogla dzialac jest dla mnie jakies takie nieleganckie w kodzie. Pozdrawiam
komentarz 30 marca 2016 przez criss Mędrzec (172,590 p.)
Nie tylko, żeby jedna mogła działać. Niezależnie też możesz jej używać. Nieeleganckim bym tego nie nazwał.. Najwyżej możesz zamknąć je w klasie i pierwszy print niech będzie private.
0 głosów
odpowiedź 30 marca 2016 przez Radfler VIP (101,030 p.)

To ja jeszcze dodam swoją implementację funkcji print:

template<typename... Args>
void print(Args&&... args) {
    (std::cout << ... << std::forward<Args>(args));
}

Wykorzystuje ona fold expression (część standardu C++17): http://coliru.stacked-crooked.com/a/7a27e0c76564d3f3

Dam jeszcze wersję z tablicą (podobną do tej od użytkownika @MetGang):

template<typename... Args>
void print(Args&&... args) {
    
    using Swallow = bool[];
    static_cast<void>(Swallow{(std::cout << std::forward<Args>(args), false)...});
    
}

http://coliru.stacked-crooked.com/a/7a27e0c76564d3f3

komentarz 30 marca 2016 przez criss Mędrzec (172,590 p.)
A tak, też ktoś w temacie na SO podlinkowanym przez MetGang'a wspomniał o takim rozwiązaniu od cpp17
komentarz 31 marca 2016 przez adrian17 Ekspert (346,320 p.)
Też o tym myślałem, ale nie widzę trywialnego sposobu by dodać ", " pomiędzy elementami.
0 głosów
odpowiedź 1 kwietnia 2016 przez 0xf Dyskutant (8,180 p.)

To cos jeszcze dodam do wyzwania jak sprawic by ten kod dzialal :

#include <iostream>
#include <utility>

template <typename Arg, typename... Args>
void doPrint(std::ostream& out, Arg&& arg, Args&&... args)
{
    out << std::forward<Arg>(arg);
    using expander = int[];
    (void)expander{0, (void(out << ',' << std::forward<Args>(args)),0)...};
}

int main()
{

int a,b,c;

    doPrint(std::cin,a,b,c);

    return 0;
}

 

komentarz 1 kwietnia 2016 przez adrian17 Ekspert (346,320 p.)
Nie rozumiem pytania. Czemu próbujesz "drukować" z użyciem std::cin?
komentarz 2 kwietnia 2016 przez 0xf Dyskutant (8,180 p.)
to zmienmy nazwe funkcji na get, problem w tym ze program nie dziala nie kompiluje sie :(
komentarz 2 kwietnia 2016 przez adrian17 Ekspert (346,320 p.)
No i całkiem słusznie, z tego samego powodu dla którego "std::cin << 123" nie ma sensu. Nie rozumiem co chcesz osiągnąć.
komentarz 2 kwietnia 2016 przez 0xf Dyskutant (8,180 p.)
Racja nie zauwazylem sprobuje to zmienic i zobacze czy zadziala
komentarz 2 kwietnia 2016 przez 0xf Dyskutant (8,180 p.)
Dobra mimo wszystko nie dziala moze dlatego ze nie wiem co robi out . Niestety nie moge znalesc tego bo google mi daje out z biblioteki fstream albo cout z iostream
komentarz 2 kwietnia 2016 przez adrian17 Ekspert (346,320 p.)
Nie pomogę dopóki w końcu nie powiedz co w ogóle chcesz osiągnąć...
komentarz 3 kwietnia 2016 przez 0xf Dyskutant (8,180 p.)
Chce miec funkcje ktora wczytuje od nas podane jako parametry zmienne.

Chce napisac swojego cin>> tyle ze zamist robic to cin>>a>>b i tak dalej to MojaFunckja(a,b,c,d,e)
komentarz 3 kwietnia 2016 przez adrian17 Ekspert (346,320 p.)

Czyli pytasz jak zmienić kod który drukuje na kod który ma wczytywać. Ugh... tak samo, jak każde inne użycie std::cin,

  • cout to ostream, cin to istream
  • cout używa <<, cin używa >>
  • cout << ',' ma sens, cin >> ',' nie ma więc trzeba to wyrzucić.

Podobne pytania

0 głosów
1 odpowiedź 488 wizyt
pytanie zadane 25 listopada 2017 w C i C++ przez Postal192 Początkujący (270 p.)
+4 głosów
3 odpowiedzi 630 wizyt
pytanie zadane 31 grudnia 2016 w Nasze projekty przez KubenQPL Maniak (62,820 p.)
0 głosów
3 odpowiedzi 477 wizyt
pytanie zadane 30 lipca 2016 w Rozwój zawodowy, nauka, praca przez Zahrad Użytkownik (740 p.)

92,696 zapytań

141,607 odpowiedzi

320,114 komentarzy

62,055 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...