std::tuple – odpowiedź na zwracanie w stylu C kilku wartości z funkcji

Dość częstym pytaniem które początkujący programista może sobie zadawać, jest „jak mogę zwrócić kilka argumentów z funkcji”? Najczęstszą odpowiedzią jest „użyj struktury/klasy”. I to jest dobra odpowiedź, bo podpowiada takiemu programiście, że może istnieć powiązanie pomiędzy zwróconymi danymi, np. w grze FPS, dla jakiegoś identyfikatora dla broni, chcemy także wyciągnąć wszystkie informacje o ilości naboi w magazynku, ogólnej ilości pocisków, częstotliwości strzelania, mocy pocisku. Wtedy zamiast wyciągać po kolei kolejne informacje, tworzymy obiekt będący reprezentacją broni. Ma to wiele zalet, np. możliwość utworzenia sensownych powiązań pomiędzy różnymi elementami programu, enkapsulację oraz wspomaganie Reguły Jednej Odpowiedzialności (SRP, single responsibility principle)

Nie nadaje się to jednak dla kilku wartości, które są luźno powiązane ze sobą:

div_t wynik = div(5, 2);
obj.wart = wynik.quot;
obj.inna = wynik.rem;

Zauważ, że trzeba wypakować strukturę po jej stworzeniu. Ale da się jeszcze bardziej wygłupić, za pomocą argumentów wyjściowych:

uchar red, green, blue;
Fl::get_color(color, red, green, blue);

Tutaj program wydaje się prostszy… aż do momentu gdy programista chce wstawić te liczby nie do zmiennych typu uchar, lecz do intów. Wtedy jeszcze trzeba podstawić do odpowiednich wartości. Ponadto można łatwo nadziać się tutaj – programiści C bardzo nie lubią takiego kodu – nie widzą czy red, green, blue zmieni zawartość po wywołaniu funkcji – woleliby użycie tutaj wskaźników. (o tym, czy ich obawy są zasadne, to temat na inny wpis). Mamy w sumie ponadto drugi problem – gdyby funkcja przyjmowała te argumenty przez wartość, mielibyśmy tu bezsensowny kod, ale jeszcze mający niezdefiniowane zachowanie – użyto niezainicjalizowanych zmiennych. Ponadto, nie możesz zignorować części danych, musisz je gdzieś wstawić do zmiennych. Generalnie słabizna.

I tu do gry wkracza std::tuple (tuple, po polsku „krotka”), ugólnienie klasy std::pair na dowolną liczbę przechowywanych wartości –

std::tuple<int, int> divmod(int a, int b)
{
    return { a/b, a%b };
}

int main()
{
    auto reszta = divmod(5, 2);
    std::cout << std::get<0>(reszta) << "  " << std::get<1>(reszta); // wypisze: 2  1
}

Możemy też podstawić zwrócone wartości do innych zmiennych:

long wynik;
long reszta;
std::tie(wynik, reszta) = divmod(5, 2);

Albo zignorować część wyniku:

long wynik;
std::tie(wynik, std::ignore) = divmod(5, 2);

To tylko wąski zakres możliwości dla std::tuple. Z std::tuple można by stworzyć „structy 2.0”, czyli nazwane krotki:

struct Osoba : public std::tuple<int, std::string, std::string>
{
    auto wiek() -> decltype((std::get<0>(*this)) { return std::get<0>(*this); }
    auto imie() -> decltype((std::get<1>(*this)) { return std::get<1>(*this); }
    auto nazwisko() -> decltype((std::get<2>(*this)) { return std::get<2>(*this); }
}

W ten sposób można udostępnić zarówno dostęp do pól po nazwach, jak po indeksach. A dzięki indeksom i odrobinie metaprogramowania z szablonami można robić na przykład pętle czasu kompilacji:

Osoba osoba(24, "Jan", "Kowalski");
for_each_tuple_element(osoba, [](auto& x) // auto jako typ argumentu to funkcjonalność C++14
{
    std::cout << x << "\n";
});

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax