Niejawna konwersja niedozwolona po powrocie

21
#include <optional>

bool f() {
  std::optional<int> opt;
  return opt;
}

Nie kompiluje: 'return': cannot convert from 'std::optional<int>' to 'bool'

Odniesienie do konsultacji Chciałbym znaleźć wyjaśnienie, ale przeczytałem je tak, jak powinno być dobrze.

Konwersje niejawne są wykonywane za każdym razem, gdy wyrażenie pewnego typu T1 jest używane w kontekście, który nie akceptuje tego typu, ale akceptuje niektóre inne typy T2; w szczególności:

  • gdy wyrażenie jest używane jako argument podczas wywoływania funkcji zadeklarowanej za pomocą T2 jako parametru;
  • gdy wyrażenie jest używane jako operand z operatorem, który oczekuje T2;
  • podczas inicjowania nowego obiektu typu T2, w tym instrukcji return w funkcji zwracającej T2;
  • gdy wyrażenie jest używane w instrukcji switch (T2 jest typem integralnym);
  • gdy wyrażenie jest używane w instrukcji if lub w pętli (T2 to bool).
darune
źródło
7
Niejawne konwersje są wykonywane” , ale operator bool()o std::optionalto explicit.
Jarod42

Odpowiedzi:

22

std::optionalnie ma możliwości niejawnej konwersji na bool. (Zezwalanie na niejawne konwersje booljest ogólnie uważane za zły pomysł, ponieważ booljest to typ integralny, więc coś w rodzaju int i = optskompilowałoby się i zrobiłoby całkowicie niewłaściwą rzecz).

std::optional nie mają „kontekstowe” do konwersji Bool, definicja, która wygląda podobnie do operatora Obsada: explicit operator bool(). Nie można tego użyć do niejawnych konwersji; ma zastosowanie tylko w niektórych szczególnych sytuacjach, w których oczekiwany „kontekst” jest wartością logiczną, jak warunek instrukcji if.

To czego chcesz opt.has_value().

Sneftel
źródło
4

Z dokumentów C ++ :

Gdy obiekt typu opcjonalny <T> jest kontekstowo konwertowany na bool, konwersja zwraca true, jeśli obiekt zawiera wartość, i false, jeśli nie zawiera wartości.

Przeczytaj o konwersjach kontekstowych tutaj :

W następujących kontekstach oczekiwany jest typ bool i niejawna konwersja jest wykonywana, jeśli deklaracja bool t (e); jest dobrze sformułowany (to znaczy jawna funkcja konwersji, taka jak jawna T :: operator bool () const; jest brana pod uwagę). Mówi się, że takie wyrażenie e jest kontekstowo konwertowane na bool.

  • kontrolujące wyrażenie if, while, for;
  • operandy wbudowanych operatorów logicznych !, && i ||;
  • pierwszy operand operatora warunkowego?:;
  • predykat w deklaracji static_assert;
  • wyrażenie w specyfikatorze noexcept;
  • wyrażenie w wyraźnym specyfikatorze;

Możesz wykonać następujący hack:

bool f() {
    std::optional<int> opt;
    return opt || false;
}

ponieważ konwersja kontekstowa ma miejsce w przypadku wbudowanych operatorów logicznych, ale konwersja kontekstowa nie zawiera returninstrukcji i std::optionalsama w sobie nie ma niejawnej konwersji na bool.

Dlatego najlepiej byłoby użyć std::optional<T>::has_value:

bool f() {
    std::optional<int> opt;
    return opt.has_value();
}
Orzechówka
źródło
co return {opt}? lubreturn bool{opt};
darune
3
@darune return {opt};nie będzie działać, ale return static_cast<bool>(opt);czy return bool{opt};będzie działać. Sugeruje się jednak użycie has_valuefunkcji członka, ponieważ naprawdę pokazuje wyraźną intencję tego, co chcesz zrobić
NutCracker
Lub słynny return !!pot;hack ( has_valuelepszy)
LF
1

Wynika to z faktu, że niejawna kowalencja std :: opcjonalna do bool nie jest obsługiwana: https://en.cppreference.com/w/cpp/utility/optional/operator_bool

constexpr operator jawny bool () const noexcept;

Musisz jawnie przekonwertować na bool jako bool(opt)lub po prostu użyj opt.has_value()zamiast tego.

theWiseBro
źródło
bool {opt} również działa i powinien być
lepszy
1

Tak naprawdę nie chodzi o niejawną konwersję, chodzi o rodzaj inicjalizacji.

To, co opcjonalne, ma jawną funkcję konwersji, tj

explicit operator bool() const; 

Z N4849 [class.conv.fct] / p2

Funkcja konwersji może być jawna (9.2.2), w którym to przypadku jest traktowana tylko jako konwersja zdefiniowana przez użytkownika do bezpośredniej inicjalizacji.

Powyższe oznacza, że ​​w tych przypadkach będzie używana funkcja konwersji: [dcl.init] / p16

Inicjalizacja, która ma miejsce (16.1) - dla inicjalizatora, który jest nawiasową listą wyrażeń lub listą stężeń inicjowanych, (16.2) - dla nowego inicjalizatora (7.6.2.7), (16.3) - w wyrażeniu static_cast ( 7.6.1.8), (16.4) - w konwersji typu notacji funkcjonalnej (7.6.1.3) i (16.5) - w postaci stanu warunkowego nazywa się inicjowaniem bezpośrednim.

Jednak te przypadki nie będą korzystać z funkcji konwersji: [dcl.init] / p15

Inicjalizacja zachodząca w postaci inicjatora lub warunku nawiasowego lub równego (8.5), a także przekazywanie argumentów, zwracanie funkcji, zgłaszanie wyjątku (14.2), obsługa wyjątku (14.4) i inicjowanie elementu (9.4.1), nazywa się inicjowaniem kopii.

Przykład w pytaniu dotyczy przypadku inicjalizacji kopii i nie wykorzystuje opcjonalnej funkcji konwersji.

Trixie
źródło