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.
CodePudding user response:
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
CodePudding user response:
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