O que é variant em C++? Conheça a “nova” ferramenta do C++17

Variant em C++ - a nova ferramenta do C++ 17

Você já se perguntou se é possível utilizar uma mesma variável para armazenar tipos diferentes em C++ (à semelhança do que se faz em Python)? Se sim, eu também, e acabei descobrindo que introduziram o variant em C++17 exatamente para esse fim.

O variant é um tipo de variável que pode armazenar dentro de si valores de diversos outros tipos (ao contrário das variáveis normais, que possuem apenas um tipo desde sua criação), como em uma união de tipos, mas apenas um desses tipo pode ser armazenado no variant por vez.

A sintaxe para se criar uma variável de tipo variant é mostrada no quadro a seguir.

std::variant<tipo1, tipo2, tipo3…> nomeDaVariavel;

Vejamos a seguir um exemplo do uso do variant em C++.


Exemplo 1 – Uso do std::variant em C++

#include <variant>
#include <string>
#include <iostream>

using namespace std;

int main()
{
    std::variant<int, string, bool> variante{};
    cout << "O valor de variante usando o tipo int eh: " << get<int>(variante) << endl;
    cout << "O valor de variante usando o indice do tipo int eh: " << get<0>(variante) << endl;
    variante = "e pode isso?";
    cout << "O valor de variante agora eh: " << get<string>(variante) << endl;
    variante = false;
    cout << "Variante apos a modificacao para bool: " << std::boolalpha << get<bool>(variante) << endl;
}

O valor de variante usando o tipo int eh: 0
O valor de variante usando o indice do tipo int eh: 0
O valor de variante agora eh: e pode isso?
Variante apos a modificacao para bool: false

No exemplo acima podemos ver a criação da variável variante do tipo std::variant<int, std::string, bool>, ou seja: ela é um variant que pode armazenar em si valores inteiros, string ou booleanos.

Note ainda que eu não inicializei variante, portanto ela foi inicializada com a inicialização padrão do tipo std::variant, que funciona da seguinte forma: o valor padrão do primeiro tipo do variant (neste caso, o tipo int) é armazenado na variável – isto é, o valor 0.

Devido a esse comportamento dos variant, é necessário que o primeiro tipo do variant seja default constructible, isto é, que ele possa ser construido com um valor padrão (as classes criadas pelo usuário com o construtor padrão marcado como = delete, por exemplo, não satisfazem essa condição).


Como acessar os valores de um variant em C++?

Vemos no exemplo 1 que as funções get<tipo> e get<índice-do-tipo> podem ser usadas para se obter o valor contido no variante. Basta passar o tipo contido no variant no momento, ou o índice do tipo tal qual ele aparece na declaração do variant dentro dos <> (no nosso caso, int tem índice 0, std::string tem índice 1, e bool tem índice 2), e voilà!


Como lidar com erros usando std::variant em C++?

O que acontece, contudo, se tentarmos acessar o valor de um tipo que não está – no momento – armazenado no variant?

No exemplo 2 eu faço isso (ou tento), e nós vemos que o programa emite uma exceção do tipo std::bad_variant_access, e a mensagem de erro obtida nos indica que tentamos acessar o índice errado do variante.


Exemplo 2 – Exceções com std::variant em C++

#include <variant>
#include <string>
#include <iostream>

using namespace std;

int main()
{
    variant<int, string, bool> variante{10};
    try {
        const string valorStr = get<string>(variante);
    } catch (const std::bad_variant_access& e) {
        cout << "Erro - " << e.what() << endl;
    }
}

Erro – std::get: wrong index for variant


Como acessar os valores do variant em C++ de forma segura?

Vimos no exemplo 2 que tentar acessar os valores do variant de forma despreocupada pode resultar em uma catástrofe. Se assim o é, não haveria algum modo de tentar acessar os valores de um variant de modo seguro? Sem que o programa emita exceções? Se você respondeu que sim, tem razão, um mecanismo assim existe mesmo.

As funções get_if<tipo ou indice> e holds_alternative<tipo> permitem acessar o valor do variante de forma segura.

get_if

A função get_if possui duas sobrecargas no que diz respeito aos seus parâmetros template, uma que aceita o tipo que se deseja acessar, e outra que aceita o índice do tipo que se deseja acessar. Ambas recebem um único argumento: um ponteiro para objeto de tipo variant.

O get_if retorna um ponteiro para o valor que se deseja acessar, caso ele seja o valor da vez armazenado no variant, ou um nullptr, caso contrário.

holds_alternative

A função holds_alternative<tipo> faz algo parecido com a get_if, mas ao invés de retornar o valor do tipo desejado caso ele esteja armazenado na variante, ela retorna true, e false caso contrário. Na entrada ela recebe diretamente o objeto variant que se deseja verificar. Simples assim.

Vejamos no exemplo 3 o uso dessas duas funções.


Exemplo 3 – Acesso seguro com get_if e holds_alternative

#include <variant>
#include <string>
#include <iostream>

using namespace std;

int main()
{
    variant<int, string, bool> variante{10};
    
    // retorna nullptr
    const string* valorStr = std::get_if<std::string>(&variante);
    if (!valorStr) {
        cout << "O valor armazenado no variante no momento não é de tipo string\n";
    }
    
    if (const int* valorInt = std::get_if<0>(&variante); valorInt) {
        cout << "O valor armazenado no variante no momento é o int " << *valorInt << endl;
    }
    
    // holds_alternative retorna true se o variante guardar um valor do tipo
    // bool, e falso caso contrário.
    if (!std::holds_alternative<bool>(variante)) {
        cout << "O valor armazenado no variante no momento não é de tipo bool\n";
    }
}

O valor armazenado no variante no momento não é de tipo string
O valor armazenado no variante no momento é o int 10
O valor armazenado no variante no momento não é de tipo bool


Próximos passos

Agora que sabemos o que é o variant em C++ e para que ele serve, assim como as formas de utilizá-los de modo seguro e como gerenciar os erros que podem surgir do seu uso, nos perguntamos qual é a próxima etapa, sim?

Na sequência do tema dos variants, falarei sobre o uso do design pattern (ou padrão de design) visitor que pode ser usado para acessar os valores armazenados em um variant sem precisar verificar qual é o tipo da vez contido no variant.

Ficou curioso? Se sim, inscreva-se na nossa newsletter para receber o artigo no seu email assim que ele for lançado.

Foto de perfil de Emanoel

Sou apaixonado por tecnologia, literatura e também filosofia. O cultivo dessas paixões ao longo da minha trajetória me inspiraram a compartilhar aquilo que aprendo com os outros da maneira mais clara que eu possa. Sou formado em Engenharia Elétrica no Brasil, e também sou engenheiro formado na França. Trabalho atualmente como programador C++ em uma multinacional francesa, uma das maiores empresas de TI do mundo.

Leave a Reply

Your email address will not be published. Required fields are marked *