Zwracanie referencji z funkcji

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

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

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:

template < template<typename ...> class C, typename T>
T &minOfContainer(C<T>& container) {
    return *std::min_element(container.begin(), container.end());
}
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?

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.

0

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

1

https://wandbox.org/permlink/waRIPnPb3izSA8Az

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));
}
1
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.

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.

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 ?

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ć]

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;
}
0

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

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

minOfContainer<std::vector>(x);
1
template<template <typename...> class C, typename T, typename... Types>
T& minOfContainer(C<T, Types...>& container)
{
    return *std::min_element(container.begin(), container.end());
}
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ć...

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/

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ć.

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

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.

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.

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

 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 ;)

0

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

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.

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.

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 ...> ?
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.

0

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

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?

0

Ogółem to raczej materiał na nowy temat. Odpowiedź: Przekazywanie parametru przez wartość i referencję

1 użytkowników online, w tym zalogowanych: 0, gości: 1