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:około 11 godzin
  • 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:około 11 godzin
  • 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:około 11 godzin
  • 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:około 11 godzin
  • 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:około 11 godzin
  • 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 16 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:około 11 godzin
  • 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:około 11 godzin
  • 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:około 11 godzin
  • 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:około 11 godzin
  • Lokalizacja:Szczecin
0

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


pingwindyktator
  • Rejestracja:ponad 12 lat
  • Ostatnio:około miesiąc
  • 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:około 11 godzin
  • 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:2 minuty
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:około 11 godzin
  • Lokalizacja:Szczecin
0

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


Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)