Zwracanie referencji z funkcji

Zwracanie referencji z funkcji
PI
  • Rejestracja:ponad 7 lat
  • Ostatnio:około 5 lat
  • Postów:55
0

Witajcie

Mam na ćwiczeniach z programowania obiektowego takie zadanie:

(C++14) Napisz szablon funkcji, która przyjmie jako argument referencję na jednoargumentowy kontener. Funkcja powinna zwrócić referencję na najmniejszy element tego kontenera.

Mój ćwiczeniowiec rozwiązał to tak jak poniżej

Kopiuj
template < template<typename ...> class C, typename T>
T &minOfContainer(C<T> container) {
    T &min=*container.begin();
    for(T &current : container) {
        if(current < min)
            min=current;
    }
    return min;
}

Mam pewne wątpliwości co do poprawności tego rozwiązania.
Z tego co wiem ( a wiem niewiele ) to zmienna referencyjna jest aliasem do innej zmiennej, inną nazwą tego samego miejsca w pamięci. Wyczytałem także, że zmienna referencyjna jest inicjowana raz przy deklaracji i nie powinna być zmieniana. W powyższym kodzie jest ona traktowana jak normalna zmienna. Nie mogę tego zrozumieć.
Czy jest mi ktoś w stanie to wyjaśnić?

Pozdrawiam

edytowany 1x, ostatnio: flowCRANE
kq
Moderator C/C++
  • Rejestracja:prawie 12 lat
  • Ostatnio:minuta
  • Lokalizacja:Szczecin
7

Na pewno tak to rozwiązał? Bo jeśli tak, to jest zwyczajnym głąbem i powinien się wybrać na kurs C++, zamiast uczyć dezorientować ludzi.

  1. Przyjmuje kontener przez kopię (!)
  2. min to referencja do pierwszego elementu kontenera, i to on będzie modyfikowany
  3. Jak kontener ma 0 elementów, to jest to UB
  4. Zwracana jest referencja do obiektu lokalnego = UB
  5. Jest coś takiego jak std::min_element, pisanie pętli ręcznie jest błędem. A jeśli już, to powinien użyć std::less.
  6. typename ... jednoargumentowy?

Poprawna implementacja:

Kopiuj
template < template<typename ...> class C, typename T>
T &minOfContainer(C<T>& container) {
    return *std::min_element(container.begin(), container.end());
}

edytowany 2x, ostatnio: kq
MO
@kq: "głąbem" no no... radykalizujesz się :-)
kq
Ech, to jest ekstremalny przykład niekompetencji, osoba tworząca taki kod nie powinna być juniorem C++, a co dopiero go nauczać. :​[
CZ
Cud, ze na jakiejś polskiej uczelni jest c++ młodszy niz 98 a Ty tu narzekasz jeszcze :D.
PI
  • Rejestracja:ponad 7 lat
  • Ostatnio:około 5 lat
  • Postów:55
0

Dzięki za szybką odpowiedź.
No tak, dla kogoś znającego dobrze mechanizmy C++ i funkcje STL zadanie nabiera sensu.
Czyli powyższe rozwiązanie nawet gdyby zmienić referencje na wskaźniki, to i tak nie będzie poprawne?

kq
Moderator C/C++
  • Rejestracja:prawie 12 lat
  • Ostatnio:minuta
  • Lokalizacja:Szczecin
1

Zależy od ćwiczeniowca ;​)

Na pewno nie pasuje przyjmowanie referencji (bo przyjmowana jest kopia), i jeden argument (ale to można dość prosto zmienić, tyle że chyba nie ma takich kontenerów w bibliotece standardowej). Użycie wskaźników jest w tym zadaniu zbędne.

Użycie ręcznej pętli zamiast min_element w ramach zadania studenckiego uważam za akceptowalne, choć sugerowałbym użycie gotowej funkcji bibliotecznej gdy taka jest.

Aha, jeszcze jedna sprawa: to zadanko to UB w przypadku pustego kontenera, taka jest cena zwracania referencji.


edytowany 1x, ostatnio: kq
PI
  • Rejestracja:ponad 7 lat
  • Ostatnio:około 5 lat
  • Postów:55
0

Mam jeszcze problem z wywołaniem funkcji, którą podesłałeś. Jak powinno wyglądać wywołanie dla std::list<int> ?

kq
Moderator C/C++
  • Rejestracja:prawie 12 lat
  • Ostatnio:minuta
  • Lokalizacja:Szczecin
1

https://wandbox.org/permlink/waRIPnPb3izSA8Az

Kopiuj
template<template <typename...> class C, typename T>
T& minOfContainer(C<T>& container)
{
    return *std::min_element(container.begin(), container.end());
}

int main()
{
    list<int> x{1,2,0,4};
    DBG(minOfContainer(x));
}

PI
a no właśnie, bo min_element zwraca iterator :) Dzięki, już wszystko jasne.
kq
W pierwszym poście kod pisałem z głowy, gwiazdkę dodałem później :​)
PI
a mógłbyś jeszcze zaproponować jak zrobić to przy użyciu pętli? Jakiego typu powinienem zadeklarować zmienną max, aby móc ją zwrócić jako referencje?
kq
Generalnie takie pytania to nadają się na osobny post, zamiast komentarza ;​)
kq
Moderator C/C++
  • Rejestracja:prawie 12 lat
  • Ostatnio:minuta
  • Lokalizacja:Szczecin
1
Kopiuj
template<template <typename...> class C, typename T>
T& minOfContainer(C<T>& container)
{
    T* min = &*container.begin();
    auto less = std::less<>{};
    for(auto& el : container) {
        if(less(el, *min)){
            min = &el;
        }
    }
    return *min;
}

Wersja z pętlą. Użycie less nie jest sztuką dla sztuki, bo operator< dla wskaźników nie jest w pełni zdefiniowany, a kontenery wskaźników to nic nadzwyczajnego.


PI
Oj chyba ja nie czuję C++ . Dzięki wielkie, wymiatasz :)
Blooser
Co dokładnie oznacza &*?
kq
W tym kontekście oznacza to, że container jest referencją.
Blooser
Chodziło mi o &* o te dwa znaki, sformatowało mi źle odpowiedź :< Spotkałem się kiedyś z kodem, gdzie w funkcji argument był przekazywany z użyciem &*. np void foo(&* bar), coś takiego jest poprawne?
kq
Ach. begin() zwraca iterator, a my chcemy wskaźnik. * robi dereferencję, czyli otrzymujemy referencję, a potem z niej wyciągmy wskaźnik za pomocą &.
AL
  • Rejestracja:prawie 11 lat
  • Ostatnio:około 3 lata
  • Postów:1493
1

Przy czym dla pustego kontenera to nadal chyba będzie UB i należałoby w takim przypadku rzucić wyjątek albo zmienić sygnaturę funkcji żeby zwracała np. optionala.

kq
Toż to napisałem wyżej ;​) Zakładam, że zachowanie funkcji z zadania jest nie do zmiany.
AL
@kq tak, wiem, widziałem, chciałem po prostu to jasno zaznaczyć, żeby nie było wątpliwości. :)
ZE
  • Rejestracja:prawie 7 lat
  • Ostatnio:ponad 6 lat
  • Postów:6
0

To ja jeszcze mam pytanie. Cały kod podany przez KQ skopiowałem do visual studio 2017 a ten na mnie krzyczy, że: "error C2782: 'T &minOfContainer(C<T> &)': template parameter 'C' is ambiguous".
Można to jakoś skompilować przy pomocy visuala ?

kq
Kurde, rzeczywiście. Zobaczę co da się zrobić.
koszalek-opalek
  • Rejestracja:około 9 lat
  • Ostatnio:ponad 2 lata
0
zeszyt napisał(a):

To ja jeszcze mam pytanie. Cały kod podany przez KQ skopiowałem do visual studio 2017 a ten na mnie krzyczy, że: "error C2782: 'T &minOfContainer(C<T> &)': template parameter 'C' is ambiguous".
Można to jakoś skompilować przy pomocy visuala ?

Może tak pójdzie? [nie zdążyłem przetestować]

Kopiuj
template<typename C>
auto & minOfContainer(C & container)
{
    auto min = &*container.begin();
    auto less = std::less<>{};
    for(auto& el : container) {
        if(less(el, *min)){
            min = &el;
        }
    }
    return *min;
}
kq
pewnie pójdzie, bo cl.exe wydaje się wykładać na dedukcji typu (legalnej), ale zdaje się to niezgodne z zadaniem
koszalek-opalek
@kq: Jak to niezgodne? Było "Napisz szablon funkcji, która przyjmie jako argument referencję na jednoargumentowy kontener" -- no a przecież przyjmie. :) Tyle, że jest ogólniejsza... :)
kq
Hm, w sumie... Z założenia jednoargumentowy powinno wykluczać niejednoargumentowe, ale w takim wypadku std::list i std::vector odpadają :​D
kq
Moderator C/C++
  • Rejestracja:prawie 12 lat
  • Ostatnio:minuta
  • Lokalizacja:Szczecin
0

cl.exe wykłada się na dedukcji typu (poprawnej).

Możesz wywołać funkcję podając szablon explicite:

Kopiuj
minOfContainer<std::vector>(x);

MO
  • Rejestracja:około 10 lat
  • Ostatnio:około 7 godzin
  • Lokalizacja:Tam gdzie jest (centy)metro...
1
Kopiuj
template<template <typename...> class C, typename T, typename... Types>
T& minOfContainer(C<T, Types...>& container)
{
    return *std::min_element(container.begin(), container.end());
}

Każdy problem w informatyce można rozwiązać, dodając kolejny poziom pośredniości,z wyjątkiem problemu zbyt dużej liczby warstw pośredniości — David J. Wheeler
kq
Moderator C/C++
  • Rejestracja:prawie 12 lat
  • Ostatnio:minuta
  • Lokalizacja:Szczecin
0

Na ircu mi mówią, ze najnowsza wersja kompilatora powinna to łykać (od około pół roku). Chyba da się w VS update kompilatora zrobić...


vpiotr
  • Rejestracja:ponad 13 lat
  • Ostatnio:prawie 3 lata
0

TL;DR
Nie dajmy się tak do końca zwariować z tą fobią przed UB, skoro sami twórcy biblioteki standardowej podobne rozwiązanie dopuszczają.

http://www.cplusplus.com/reference/vector/vector/front/

edytowany 1x, ostatnio: vpiotr
kq
Moderator C/C++
  • Rejestracja:prawie 12 lat
  • Ostatnio:minuta
  • Lokalizacja:Szczecin
0

To nie jest fobia. Jeśli funkcja ma preconditions mówiące x, to możemy dopuścić, że ich złamanie to UB i nie dodajemy dodatkowego sprawdzania. Ale na pewno należy o tym pamiętać.


vpiotr
  • Rejestracja:ponad 13 lat
  • Ostatnio:prawie 3 lata
0

Po prostu funkcje w rodzaju min, front, find powinny zwracać nie referencję a std::optional. I byłoby po kłopocie.
Ale znowu standardowy optional nie bardzo to wspiera:

There are no optional references; a program is ill-formed if it instantiates an optional with a reference type. Alternatively, an optional of a std::reference_wrapper of type T may be used to hold a reference. In addition, a program is ill-formed if it instantiates an optional with the tag types std::nullopt_t or std::in_place_t.

Pozostaje boost::optional

Java to częściowo ma: http://www.technicalkeeda.com/java-8-tutorials/java-8-stream-min-and-max

kq
Moderator C/C++
  • Rejestracja:prawie 12 lat
  • Ostatnio:minuta
  • Lokalizacja:Szczecin
2

Ale to jest narzut dodatkowego sprawdzania, którego możesz nie chcieć mając pewność, że warunki są spełnione (stąd różnica między np. [] i .at() wektora). To niestety nie jest proste zagadnienie z oczywistymi rozwiązaniami.


Zobacz pozostały 1 komentarz
vpiotr
Ale niech no znajdę jakieś popołudnie wolne :) O ile nikt tego jeszcze nie napisał to chyba warto było by. Nazwa robocza: optional_ref<T>.
pingwindyktator
template <typename T> using optional_ref = std::optional<std::reference_wrapper<T>>;. Proszę, zrobiłem to za Ciebie :p
vpiotr
I to jest ta wersja bez narzutu, tak?
pingwindyktator
Jeśli chcesz mieć sprawdzanie, czy kontener jest pusty, to zawsze musisz sie pogodzić z narzutem takiego sprawdzania. Odwoływałem się do "standardowy optional nie bardzo to [optional ref] wspiera"
vpiotr
@pingwindyktator: przeczytaj post który komentujesz. Chodzi o rozwiązanie podwójne - z narzutem (i walidacją) i bez narzutu.
ZE
  • Rejestracja:prawie 7 lat
  • Ostatnio:ponad 6 lat
  • Postów:6
0

To dziękuje za wszystkie odpowiedzi (Tylko nie wiem komu mam dać plusa, każda jest wartościowa a ja plusów nie rozdaje byle komu). Jeszcze dużo nauki przede mną. Można usunąć - nic nie wnosi do tematu.

kq
Plusy każdemu, kto uważasz, że zasługuje. ✓ jest tylko jeden na temat, więc najlepszej odpowiedzi.
PI
  • Rejestracja:ponad 7 lat
  • Ostatnio:około 5 lat
  • Postów:55
0

wybaczcie, trochę się w tym wszystkim już pogubiłem.
Jak to jest z tymi zmiennymi referencyjnymi? Niby gdzieś wyczytałem, że zmienną referencyjną inicjuje się raz i nie powinno się jej zmieniać, a z drugiej strony... petla zakresowa for

Kopiuj
 for(auto& v : values)     
    {         
        v *= 2;     
    }      

W kazdym obrocie zmienna referencyjna &v jest zmieniana. Jestem nieco zdezorientowany.
Jeśli zatem można zmieniac wskazania zmiennej referencyjnej, to czy pierwszy kod który wrzuciłem nie nadaje się do niczego, czy jednak skoro działa to jakiś tam sens ma ? Mówimy oczywiście na poziomie 1 roku studiów ;)

edytowany 1x, ostatnio: piotreekk
kq
Moderator C/C++
  • Rejestracja:prawie 12 lat
  • Ostatnio:minuta
  • Lokalizacja:Szczecin
0

v jest inną referencją za każdym razem, przypinaną do innej zmiennej.


pingwindyktator
  • Rejestracja:ponad 12 lat
  • Ostatnio:około 2 miesiące
  • Lokalizacja:Kraków
  • Postów:1055
0

Nie możesz "przestawić" referencji, tzn ustawić ją, żeby wskazywała na inny obiekt. Możesz oczywiście zmieniać jej wartość, zmieniając tym samym wartość "oryginalnej" zmiennej. W przykładzie, który podałeś, zmienna v jest inicjalizowana przed każdym obrotem pętli.


do not code, write prose
PI
  • Rejestracja:ponad 7 lat
  • Ostatnio:około 5 lat
  • Postów:55
0

Ooo, no i wiele rzeczy staje się jaśniejsze, dzięki za odpowiedzi.
Odnośnie tego tematu mam jeszcze dwa małe pytania. Nie jestem pewien czy dobrze rozumiem te szablony.

Kopiuj
template < template<typename ...> class C, typename T>
  1. Czy słowa typename i class można stosować zamiennie? Na różnych forach toczą się takie dyskusje, pomyślałem że przy okazji tego tematu ktoś kto się zna da mi odpowiedź.
  2. Co właściwie oznacza <typename ...> ?
kq
Moderator C/C++
  • Rejestracja:prawie 12 lat
  • Ostatnio:minuta
  • Lokalizacja:Szczecin
2

W większości przypadków (od C++17 we wszystkich) tak. Wcześniej class było obowiązkowe przy deklaracji szablonu szablonu, czyli tak jak tutaj zostało to użyte.

typename/class oznacza tylko tyle, że w danym miejscu szablonu ma się pojawić coś co jest typem (a nie wartością/szablonem), czyli np. int, a nie std::vector (szablon) lub 4 (wartość). ... oznacza, że liczba argumentów jest dowolna.


Azarien
  • Rejestracja:ponad 21 lat
  • Ostatnio:około 3 godziny
0

Wewnątrz template<> powinno się używać typename. Użycie class w tym miejscu to stara składnia.

Zobacz pozostałe 6 komentarzy
kq
Dla mnie class jest błędem tam, gdzie możemy użyć typu, który klasą nie jest - np. int. Jeśli oczekujemy wyłącznie klas to oba uważam za akceptowalne.
vpiotr
Dla mnie mylaca jest dualnosc wyboru - typename czy class. Nie mogl byc jeden sposob?
kq
Mógłby. Powinien. Ale to była dyskusja na 1993, a teraz musimy żyć z tym co jest :​(
MO
Stosuję konwencję w projektach: Jeśli arg. szablonu jest "typ rdzenny" (obejmuje POD'y, kontenery std:: lub całą resztę z std::), stosujemy słowo typename w miejscu gdzie jest dualność wyboru. Jeśli arg. szablonu to "typ projektu", stosujemy słowo class w miejscach gdzie dopuszczalna jest dualność. Dość dobrze się sprawdza. Rzut oka i wiadomo gdzie/czego szukać. Nie będę upierał się że to jedynie słuszna reguła... ot taki wybór..
kq
@Mokrowski: czyli ± to co napisałem :​D
PI
  • Rejestracja:ponad 7 lat
  • Ostatnio:około 5 lat
  • Postów:55
0

https://wandbox.org/permlink/1RN5YFdWNz1QjhBW

Nie wiem czy powinienem taką ilośc kodu wrzucać na forum, czy też w linku, dlatego na razie daje tylko w linku.

Ciąg dalszy wątpliwości. Dlaczego pierwsza funkcja modyfikuje zawartość tablicy, zaś druga funkcja odnosząca się do kontenerów nie modyfikuje zawartości, chociaż działa na takiej samej/ podobnej zasadzie?
To zachowanie programu jest uzasadnione?

kq
Moderator C/C++
  • Rejestracja:prawie 12 lat
  • Ostatnio:minuta
  • Lokalizacja:Szczecin
0

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.