0

I am looking at using std::variant to store basic types such as float, int, float2, float2, bool2, bool4, etc. which is rather trivial, but I would also ideally like to construct variant objects holding a pointer to a typed array. I came up with this solution, which compiles and run without crashing (which doesn't mean a bug isn't in there)):

#include <variant>
#include <vector>
#include <iostream>
#include <algorithm>

struct float3
{
    float x, y, z;
    float3() : x(0), y(0), z(0) {}
    float3(const float& r) : x(r), y(r), z(r) {}
};

int main()
{
    typedef std::variant<float, float3, float3*> Variant;
    std::vector<float3> v(3);
    std::generate(v.begin(), v.end(), [n=0] () mutable { return ++n; });

    Variant var(&v[0]);

    if (float3** ptr = std::get_if<float3*>(&var)) {
        std::cout << "this is a float3*: " << (*ptr)[0].x << std::endl;
    }
    else {
        std::cout << "didn't recognize type\n";
    }
    return 0;
};

However, by doing so, I lose some additional data that I'd like to store alongside this particular variant type such as the size of the array (in a number of bytes for example). Would it best to write my own custom Variant class and create a constructor like this:

class Variant 
{
public:
    enum Type
    {
        ...
        FLOAT,
        FLOAT2,
        FLOAT3,
        ...
    };
    Variant(std::shared_ptr<void*>& ptr, Type type, size_t size, size_t stride, ...) : 
        type(Type), 
        data(new Data(ptr, size, stride, ...)) 
    {}
    std::shared_ptr<Data> data;
};

With class Data

class Data
{
public:
   Data(const std::shared_ptr<void*>& ptr, size_t nb, size_t stride, ...) :
      ptr(ptr), nbytes(nb), stride(stride) ... {}
   std::shared_ptr<void*> ptr;
   size_t nbytes;
   size_t stride;
   ...
}

Or can I still somehow make it work with std::variant? Any suggestions would be greatly appreciated.

user18490
  • 3,546
  • 4
  • 33
  • 52
  • 1
    You could use `std::variant>` – user253751 Aug 08 '22 at 16:01
  • 2
    wether in a variant or not, a pointer has no information on the size of the array it might point to. Why not `std::vector` or `std::array` ? – 463035818_is_not_an_ai Aug 08 '22 at 16:04
  • 1
    A `std::variant` can hold any type that's not a reference, C-style array, or void. Whatever type you want to use to identify the data, go ahead and put it in a variant. – Drew Dormann Aug 08 '22 at 16:09
  • 1
    You should not store a pointer to the data in a vector anywhere. `&v[0]` is very unsafe and the pointer might get invalidated if the vector reallocates its internal memory. Just store the vector itself in the variant instead (or use `std::array` if the size is known at compile time) – Jakob Stark Aug 08 '22 at 17:35

2 Answers2

3

This "pointer plus size" type already exists since C++20 and it's called std::span.

You can use std::variant<float, float3, std::span<float3>>

If you want an array of many different types, you could use std::variant<std::span<float3>, std::span<float2>, std::span<bool2>, etc>. Notice that you do have to write all the types in the variant (might want to make it a typedef), but you don't have to write special code for each type as you can use visit with a template:

std::variant<std::span<float3>, std::span<float2>, std::span<bool2>> myVariant = ........;

// print all the elements. auto parameter makes a lambda with a template function.
// a separate copy is compiled for each type.
std::visit(myVariant, [](auto& span) {
    for(auto& item : span)
        std::cout << item << std::endl;
});

std::span is for an array that someone else will delete - if you want the variant to delete its own memory, use std::vector

user253751
  • 57,427
  • 7
  • 48
  • 90
  • thank you this is an interesting solution and I wasn't familiar with span (though had seen it used before). The problem is that I need to store more data than just the size of the array, like a stride or an offset but I guess I can pack this in a struct and use this as a type. Thank you very much. I will try range then). – user18490 Aug 15 '22 at 14:48
0

You can actually store a lot of info in a type. For instance a range of integers using the dimensions of an array. You don't have to actually use the storage of an array and instead just declare a pointer to such an array.

Then just extract the dimension info. Here's an example of extracting 3 dimensions from a pointer to an array:

#include <type_traits>
#include <iostream>
#include <variant>

int main()
{
    typedef int(*pa)[2][3][4];
    int ia[2][3][4];
    pa a{&ia};
    std::variant<pa, float> v(&ia);
    std::cout << std::extent<std::remove_cvref_t<decltype(*std::get<0>(v))>,0>::value << "\n";
    std::cout << std::extent<std::remove_cvref_t<decltype(*std::get<0>(v))>, 1>::value << "\n";
    std::cout << std::extent<std::remove_cvref_t<decltype(*std::get<0>(v))>, 2>::value << "\n";
}

// outputs: 2,3,4
doug
  • 3,840
  • 1
  • 14
  • 18
  • thanks I had not seen this solution before. So I learned something. Though in my case the array is dynamically allocated. – user18490 Aug 15 '22 at 14:39