O único requisito para o parâmetro de tipo do std::optional<T>
modelo de classe mencionado em [optional.optional.general] p3 é que o tipo T
seja Destructible .
Suponha que eu tenha uma classe muito restritiva que atenda a esse requisito:
class R
{
public:
~R() = default;
// This works thanks to guaranteed copy elision since C++17.
static R create()
{
return R();
}
void do_something()
{
}
private:
R() = default;
R(const R&) = delete;
R& operator=(const R&) = delete;
R(R&&) = delete;
R& operator=(R&&) = delete;
};
Mas essa classe ainda é perfeitamente utilizável:
void test_1()
{
R obj = R::create();
obj.do_something();
}
A questão é: eu realmente posso ter um objeto utilizável do tipostd::optional<R>
? Por "utilizável" eu quero dizer um objeto que contém valor. Se sim, então como eu construo esse valor ali?
O código a seguir obviamente falha porque cada construtor de R
é inacessível:
void test_2()
{
std::optional<R> o(R::create()); // Error: no matching constructor
}
Editar:
Não seria legal se std::optional
houvesse um construtor que aceitasse um construtor ?
template <typename Builder>
optional::optional(disabmiguating_tag, Builder f)
: my_internal_union(f())
{
}
com construtor apropriado, é my_internal_union
claro...
Você pode usar o tipo aa com um
operator R
:Veja um exemplo ao vivo no Compiler Explorer
Isto funciona porque o
R
que habitao
é construído como se fosse uma chamada... onde
builder
há uma referência ao queBuilder{}
passamos.operator R
retorna um pré-valor, então o retornadoR::create()
e o construídoR
são o mesmo objeto, em virtude da elisão de cópia garantida do C++17.Eu sei que a questão é c++17 , mas aparentemente c++23 você pode usar esse truque:
onde criamos um opcional fictício não vazio (neste caso
std::optional<int>
) e usamosstd::optional<T>::transform
para aplicar uma função em seu internoint
; essa função retorna oR
objeto desejado viaR::create()
, que é criado no local na memória questd::optional
mantém reservado para sua carga útil.É válido por causa da elisão de cópia obrigatória do C++17 , porque o tipo de retorno lambda é
R
, que é o mesmo tipo queR::create()
, que por sua vez é prvalue. Acho que isso significa que esseR
objeto é construído diretamente no lugar "final", que é dentro dostd::optional<R> o
que você está construindo.Provavelmente o cerne do truque é
std::construct_at
o c++20 , mas não acho que você possa usá-lo de fora, porque você não tem acesso aostd::optional
membro onde a carga útil é armazenada, e você não pode acessá-lo via UB.value()
porque isso é um UB vaziostd::optional
, e você não pode ter um não vaziostd::optional<R>
porque é exatamente isso que estamos discutindo!