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

Szablon o zmiennej liczbie parametrów C++

Object Storage Arubacloud
0 głosów
933 wizyt
pytanie zadane 29 lipca 2017 w C i C++ przez Programeł Gaduła (3,500 p.)

Witam

Moje IDE: Visual Studio 2017

kod:

#include <iostream>

using namespace std;

template<typename...T>
void Show(T...t)
{
    cout<<t<<endl;
}

int main()
{
    
    Show("");
    
    getchar();
    return 0;
}

błąd:  error C3520: "t": pakiet parametrów musi być rozwinięty w tym kontekście

komentarz 30 lipca 2017 przez Evelek Nałogowiec (28,960 p.)
Wskazówka: Czy 't' to na pewno zwykła zmienna, którą można w tej sytuacji wyświetlić?
komentarz 30 lipca 2017 przez criss Mędrzec (172,590 p.)
@Evelek - no tak, const char * bez problemu zostanie rozpoznany przez cout jako ciąg znaków i wyświetlony. Problem leży gdzie indziej.
komentarz 30 lipca 2017 przez Evelek Nałogowiec (28,960 p.)
Ale nie w tej sytuacji przy obecnym stanie kodu.
komentarz 30 lipca 2017 przez criss Mędrzec (172,590 p.)
edycja 30 lipca 2017 przez criss
Przy obecnym stanie kodu, to nie ma żadnego znaczenia - i tak sie nie kompiluje :P

3 odpowiedzi

+5 głosów
odpowiedź 30 lipca 2017 przez PoetaKodu Stary wyjadacz (10,990 p.)
wybrane 30 lipca 2017 przez Programeł
 
Najlepsza

Musisz wykorzystać tutaj pewien trick.
Najpierw kod, potem objaśnienie:

#include <iostream>
#include <utility> // dla std::forward

template <typename ArgT, typename... ArgsT>
void Show(ArgT &&firstArg, ArgsT&&... rest)
{
	std::cout << std::forward<ArgT>(firstArg);
	using expander = int[];
	(void)expander {
		(void(std::cout << std::forward<ArgsT>(rest)), 0)...
	};
}

int main()
{
	Show("A string, a number = ", 32, ", a floating point: ", 123.400f, "\n");
	return 0;
}

Funkcja Show z góry ma zadanie wyświetlać cokolwiek, więc liczba jej parametrów będzie >= 1, dlatego podajemy pierwszy argument "firstArg" oddzielnie a resztę jako listę argumentów, którą potem będziemy "rozwijali". To co może Cię zdziwić to tzw. "forwarding references" czyli "&&" - nie jest to w żadnym wypadku tutaj "&&", który używamy w ifach. Moje krótkie wyjaśnienie to: używamy rvalue reference (bo taka jest druga nazwa tego) by zapewnić możliwość zachowania typu wszystkich tych argumentów - nie kopiować ich podczas wykonywania funkcji itd. Zachęcam do przeczytania tego artykułu, świetnie to wprowadza do forwarding refs:
Forwarding references

Dalej mamy przekazanie do strumienia cout pierwszego argumentu - żeby poprawnie przekazać argument typu T&& używamy std::forward<T>. Ta linijka spowoduje, że pierwszy argument zostanie wyświetlony. Teraz dzieje się "magia":

  • dla ułatwienia zapisujemy sobie typ tablicy intów ( int[] ) jako "expander"
  • konstruujemy nienazwaną tablicę tego typu (tak samo jakbyś napisał
    int[] {0, 1, 2, 4, 5, 6};
  • (void) przed tym skonstruowaniem oznajmia kompilatorowi, że nie będziemy mieć pożytku z tej tablicy, zostanie ona zapomniana zaraz po jej skonstruowaniu
  • Teraz zerujemy każdy element tablicy a będzie ich liczba_argumentow-1 (bo pierwszy argument już został dodany)

Ostatnie zdanie może wydawać się trochę mylące, bo co innego widzimy w kodzie.
Działa to w ten sposób:
Zapisując wyrażenie (a, b) powodujemy, że zostanie wykonane "a" ale cała wartość tego nawiasu przyjmie wartość "b". Tutaj przykład:

int x = 10, y = 20, z = 30;
x = (y, z); // ostatecznie x przyjmie wartosc "z", bo jest po prawej stronie


Znając ten fakt, wystarczy tylko po prawej stronie umieścić zero (lub jakakolwiek inną wartość, ale zero tradycyjnie jest ok) a po lewej zdołać wyświetlić następny argument. Robimy to w ten sposób:

std::cout << std::forward<ArgsT>(rest)

Wartość tego wyrażenia to std::ostream&, gdyż operator << zwraca tą wartość. W takim razie, żeby zakomunikować, że pomijamy zwróconą wartość zamykamy to w voidzie:

void(std::cout << std::forward<ArgsT>(rest))

Teraz, po napisaniu tego polecenia według schematu (a, b) otrzymujemy:

(void(std::cout << std::forward<ArgsT>(rest)), 0)

Na koniec trzeba jeszcze "rozwinąć" listę argumentów, tak jak rozwija się zawinięte kartki papieru, przez operator "..." na końcu nawiasu. W ten sposób do strumienia std::cout przekażemy wszystkie argumenty zawarte w "rest".

(void(std::cout << std::forward<ArgsT>(rest)), 0)...


Mam nadzieję, że jasno to opisałem, jak coś - proszę napisać komentarz.

komentarz 30 lipca 2017 przez criss Mędrzec (172,590 p.)
edycja 30 lipca 2017 przez criss
Bardzo zgrabnie wytłumaczone. Serio, gratzy :D Świetnie tłumaczysz. Szczerze mówiąc nie widze potrzeby perfect forwarding w tym przypadku (chyba, że coś dopowiesz, bo ja nie widze), ale pewnie sam bym tak zrobił, bo ładnie :3 Daje zasłużonego lajka.

(edit)Dopisze o co mi chodzi:
O ile nikt mnie nie oszukał w toku mojej nauki :D to perfect forwarding stosuje się do zachowania dokładnie tego samego rodzaju referencji, po to, żeby, przekazane dalej ("forwarding"), utworzona została oczekiwana przez nas instancja template-a funkcji do której "forwardujemy". std::ostream::operator<< jest zwyczajnie "zoverloadowany" kilka razy - żadnych template-ów - stąd uważam, że perfect forwarding jest tu niepotrzebny.
komentarz 30 lipca 2017 przez PoetaKodu Stary wyjadacz (10,990 p.)

Perfect forwarding przydaje się prawie wszędzie.
Za "T" możesz podać swój typ, na przykład jakąś większą klasę, którą wypadałoby forwardnąć. Pamiętaj, że akceptowalne typy zależą od przeładowań operatora

std::ostream &operator<<(std::ostream&, ...) {}

Dodam jeszcze do tej mojej wypowiedzi, że w przypadku funkcji np. std::endl to nie działa, z resztą tak samo jak Wasze przykłady.

komentarz 30 lipca 2017 przez criss Mędrzec (172,590 p.)
Edytowałem komentarz wyżej - dopisałem o co mi dokładnie chodziło.
komentarz 30 lipca 2017 przez PoetaKodu Stary wyjadacz (10,990 p.)
Reference forwarding używa się do zachowania danego typu, co dobrze bardzo opisał autor artykułu, który podałem w poście. Sama budowa forward refs (z użyciem template) pozwala na przekazanie lvalue oraz rvalue bez zbędnego tworzenia kolejnych overloadów tych funkcji (co jest wymagane, by zapewnić możliwość przekazania każdego typu [który akceptuje std::cout]). Przykład: mamy jakąś dużą klasę MyObject. Przekazując sam MyObject tworzymy kopię, przekazując MyObject & nie pozwalamy na podanie rvalue, przekazując const MyObject & narażamy się na konflikt typów, jeśli dalej będziemy chcieli przekazać tę zmienną do funkcji, która przyjmuje typ MyObject & (bez const). Żeby uniknąć tego wszystkiego właśnie stosuje się forward refs, które z użyciem std::forward zapobiega konfliktom typów i niepotrzebnemu kopiowaniu.
+2 głosów
odpowiedź 30 lipca 2017 przez mokrowski Mędrzec (155,460 p.)
edycja 30 lipca 2017 przez mokrowski
#include <iostream>
 
template<typename T>
void Show(T t)
{
    std::cout << t << std::endl;
}
template<typename T, typename ...TT>
void Show(T t, TT... tt)
{
    Show(t);
    Show(tt...);
}
 
int main()
{
    Show("Ala", "ma", 12, "chomików", "po", 13.4);
}

Jeśli o to Ci chodziło.. 

komentarz 30 lipca 2017 przez criss Mędrzec (172,590 p.)
W zasadzie to wrzuciłem ten sam kod :D
komentarz 30 lipca 2017 przez mokrowski Mędrzec (155,460 p.)
W zasadzie ja także :D
+1 głos
odpowiedź 30 lipca 2017 przez criss Mędrzec (172,590 p.)

Nie możesz tak po prostu przekazać parameter packa. Musi zostać rozwinięty operatorem "trzech kropek", ale wtedy uzyskasz argumenty rozdzielone przecinkami. To, co chcesz zrobić można osiągnąć za pomocą fold expressions z c++17, ale msvc nie ma tego jeszcze zaimplementowanego o ile nie było jakieś updejtu.

Przed c++17 takie rzeczy możesz robić w taki sposób:

template<typename First>
void print(First first)
{
    std::cout << first;   
}
template<typename First, typename ...Args>
void print(First first, Args... args)
{
    std::cout << first << ' ';
    print(args...);
}

int main()
{
    print(1, 2, 3, 4, 5);
}

Podobne pytania

0 głosów
3 odpowiedzi 447 wizyt
pytanie zadane 22 lipca 2019 w C i C++ przez Luki78 Początkujący (280 p.)
0 głosów
1 odpowiedź 136 wizyt
0 głosów
2 odpowiedzi 534 wizyt
pytanie zadane 22 marca 2017 w C i C++ przez chacken Użytkownik (820 p.)

92,596 zapytań

141,446 odpowiedzi

319,720 komentarzy

61,980 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

Kolejna edycja największej imprezy hakerskiej w Polsce, czyli Mega Sekurak Hacking Party odbędzie się już 20 maja 2024r. Z tej okazji mamy dla Was kod: pasjamshp - jeżeli wpiszecie go w koszyku, to wówczas otrzymacie 40% zniżki na bilet w wersji standard!

Więcej informacji na temat imprezy znajdziecie tutaj. Dziękujemy ekipie Sekuraka za taką fajną zniżkę dla wszystkich Pasjonatów!

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!

...