Search code examples
c++coding-stylec++17variant

General questions about std::variant


I had some questions about the new std::variant type. I considered using it as a typesafe alternative to union. I wrote some test code which should compile in an online compiler. I have not found a lot of information regarding storage of types like std::array in std::variant. Raw buffer storage (e.g uint8_t [4]) does not appear to be possible with std::variant.

#include <iostream>
#include <array>
#include <variant>
#include <array>
#include <stdint.h>
#include <typeinfo>
#include <cstdlib>
using parameters_t = std::variant<uint32_t, std::array<uint8_t, 4>>;

void setComParams(parameters_t& comParameters_, uint8_t value);

int main()
{
    std::array<uint8_t, 4> test;
    parameters_t comParameters = test; // can I assign the array here directly in some way?
    auto parametersArray = std::get_if<std::array<uint8_t, 4>>(&comParameters);
    if(parametersArray == nullptr) {
        exit(0);
    }

    auto & parameterReference1 = *parametersArray; //so I can use [] indexing
    parameterReference1[1] = 5;
    parameterReference1[3] = 6;
    std::cout << "I should be 5: " << (int)(*parametersArray)[1] << std::endl;
    std::cout << "I should be 5: " << (int)parameterReference1[1] << std::endl;
    std::cout << "I should be 6: " << (int)(*parametersArray)[3] << std::endl;
    std::cout << "I should be 6: " << (int)parameterReference1[3] << std::endl;
    setComParams(comParameters, 10);
    std::cout << "I should be 10: "<< (int)(*parametersArray)[1] << std::endl;
    std::cout << "I should be 10: "<< (int)parameterReference1[1] << std::endl;

    comParameters = 16; // this should be an uint32_t now
    auto parametersArray2 = std::get_if<std::array<uint8_t, 4>>(&comParameters);
    if(parametersArray2 == nullptr) {
        std::cout << "Everything in order" << std::endl;
    }

    uint32_t * parameterNumber = std::get_if<0>(&comParameters); // using index now
    *parameterNumber = 20;
    std::cout << "I should be 20: "<< (int)*parameterNumber << std::endl;
    setComParams(comParameters,30);
    std::cout << "I should be 30: "<< (int)*parameterNumber << std::endl;
    return 0;
}

void setComParams(parameters_t &comParameters_, uint8_t value) {
    auto comParametersArray = std::get_if<std::array<uint8_t, 4>>(&comParameters_);
    if(comParametersArray == nullptr) {
        auto comParameterValue = std::get_if<uint32_t>(&comParameters_);
        if(comParameterValue != nullptr) {
            *comParameterValue = value;
        }
    }
    else {
        auto & arrayReference = *comParametersArray;
        arrayReference[1] = value;
    }
}

Just to be sure I understood everything correctly: If I use std::get, I always receive a copy, which means no matter how I do it, I cant modify the actual value of variant. Do I always have to use std::get_if in that case? Furthermore, is there any significant code bloat or overhead when using std::variant when compared to unions? Especially because I'm essentially using POD types (a union of uint32_t and uint8_t[4]).

Thanks a lot in advance.


Solution

  • You can assign/initialize a std::variant directly with a prvalue of the type that you want to emplace:

    parameters_t comParameters = std::array<uint8_t, 4>{};
    

    although this zero-initializes the array in contrast to your code.

    If you want to avoid the copy/move construction involved in this, you can use

    parameters_t comParameters(std::in_place_type<std::array<uint8_t, 4>>);
    

    for initialization or

    comParameters.emplace<std::array<uint8_t, 4>>();
    

    for assignment.

    Both still zero-initialize the std::array though. As far as I am aware there is currently no way to default-initialize an object in a std::variant.

    std::get_if returns a pointer to the the object in the std::variant. It never makes a copy of the object.

    Instead of std::get_if, you could also use std::get. std::get will just throw an exception if you try to access the wrong type and returns a reference to the contained object. It never copies the object either.:

    auto& parameterReference1 = std::get<std::array<uint8_t, 4>>(comParameters);
    

    Built-in array types or reference types or function types are not allowed in the std::variant type list, because the types are required to satisfy the concept of being destructible, meaning that the expression

    t.~T()
    

    where t is of type T must be well-formed. This is not true for built-in arrays, but for pretty much all other sensible object types.