Conversão implícita não permitida no retorno

21
#include <optional>

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

Não compila: 'return': cannot convert from 'std::optional<int>' to 'bool'

Referência de consultoria Eu teria pensado em encontrar uma explicação, mas li como deveria estar ok.

Conversões implícitas são executadas sempre que uma expressão de algum tipo T1 é usada no contexto que não aceita esse tipo, mas aceita outro tipo T2; em particular:

  • quando a expressão é usada como argumento ao chamar uma função que é declarada com T2 como parâmetro;
  • quando a expressão é usada como um operando com um operador que espera T2;
  • ao inicializar um novo objeto do tipo T2, incluindo a instrução return em uma função retornando T2;
  • quando a expressão é usada em uma instrução switch (T2 é do tipo integral);
  • quando a expressão é usada em uma instrução if ou em um loop (T2 é booleano).
darune
fonte
7
" Conversões implícitas são realizadas" , mas operator bool()de std::optionalé explicit.
Jarod42 14/02

Respostas:

22

std::optionalnão tem nenhum recurso para conversão implícita em bool. (Permitir conversões implícitas boolé geralmente considerado uma má ideia, pois boolé um tipo integral, portanto, algo como int i = optcompilaria e faria completamente a coisa errada.)

std::optional não têm uma "conversão contextual" para bool, cuja definição é semelhante a um operador de conversão: explicit operator bool(). Isso não pode ser usado para conversões implícitas; aplica-se apenas em determinadas situações específicas em que o "contexto" esperado é booleano, como a condição de uma instrução if.

O que você quer é opt.has_value().

Sneftel
fonte
4

Dos documentos C ++ :

Quando um objeto do tipo opcional <T> é convertido contextualmente em bool, a conversão retornará true se o objeto contiver um valor e false se não contiver um valor.

Leia sobre conversões contextuais aqui :

Nos contextos a seguir, o tipo bool é esperado e a conversão implícita é executada se a declaração bool t (e); é bem formado (isto é, é considerada uma função de conversão explícita, como o explícito T :: operador bool () const;). Diz-se que essa expressão e é convertida contextualmente em bool.

  • a expressão controladora de if, while, for;
  • os operandos dos operadores lógicos embutidos!, && e ||;
  • o primeiro operando do operador condicional?:;
  • o predicado em uma declaração static_assert;
  • a expressão em um especificador noexcept;
  • a expressão em um especificador explícito;

Você pode fazer o seguinte hack:

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

porque a conversão contextual ocorre no caso dos operadores lógicos internos, mas a conversão contextual não inclui returninstruções e, std::optionalpor si só, não tem conversão implícita para bool.

Portanto, seria melhor usar o std::optional<T>::has_value:

bool f() {
    std::optional<int> opt;
    return opt.has_value();
}
NutCracker
fonte
que tal return {opt}? oureturn bool{opt};
darune 14/02
3
O @darune return {opt};não funciona, mas return static_cast<bool>(opt);ou return bool{opt};funcionaria. No entanto, é sugerido o uso da has_valuefunção de membro, porque mostra realmente a intenção clara do que você deseja fazer.
NutCracker 14/02
Ou o famoso return !!pot;hack ( has_valueé melhor)
LF
1

Isso ocorre porque a cobertura implícita de std :: optional ao bool não é suportada: https://en.cppreference.com/w/cpp/utility/optional/operator_bool

operador explícito constexpr bool () const noexcept;

Você precisa converter explicitamente para bool como bool(opt)simplesmente usar opt.has_value().

theWiseBro
fonte
bool {opt} funciona tão bem e deve ser preferido sobre bool (opt)
darune 14/02
1

Não se trata realmente de conversão implícita, trata-se do tipo de inicialização.

O opcional tem uma função explícita de conversão, ou seja,

explicit operator bool() const; 

Do N4849 [class.conv.fct] / p2

Uma função de conversão pode ser explícita (9.2.2), caso em que é considerada apenas uma conversão definida pelo usuário para inicialização direta.

O exposto acima significa que esses casos usarão a função de conversão: [dcl.init] / p16

A inicialização que ocorre (16.1) - para um inicializador que é uma lista de expressões entre parênteses ou uma lista-init-braced, (16.2) - para um novo inicializador (7.6.2.7), (16.3) - em uma expressão static_cast ( 7.6.1.8), (16.4) - em uma conversão de tipo de notação funcional (7.6.1.3) e (16.5) - na forma de lista inicial de uma condição é chamada de inicialização direta.

No entanto, esses casos não usarão a função de conversão: [dcl.init] / p15

A inicialização que ocorre na forma = de uma chave ou condição de inicializador igual ou igual a (8.5), bem como na passagem de argumentos, retorno de função, lançando uma exceção (14.2), manipulando uma exceção (14.4) e inicialização de membros (9.4.1), é chamado de inicialização de cópia.

O exemplo na pergunta se enquadra no caso de inicialização da cópia e não usa a função de conversão opcional.

Trixie
fonte