Home > Mobile >  Creating a POD wrapper for non-POD types in C
Creating a POD wrapper for non-POD types in C

Time:12-07

At one point in my code I am required to pass some parameters as a POD struct (copy data to CUDA constant memory specifically). But I want to pass more "complex" types with user-defined constructors (no virtual methods).

I was wondering if there was any issue (or better solutions) doing something like this to alleviate a POD constraint (basically using a POD struct at least the same size as my object of interest as a proxy for the real thing).

#include <iostream>
#include <cstring>

// Meant to be used as a pointer to the Derived type.
template <class T>
struct PODWrapper 
{
    uint8_t data_[sizeof(T)];

    const T& operator*()  const { return *reinterpret_cast<const T*>(this); }
    const T* operator->() const { return  reinterpret_cast<const T*>(this); }
};

class NonPOD
{
    protected:

    float x_;

    public:

    NonPOD(float x) : x_(x) {}
    NonPOD(const NonPOD& other) : x_(other.x_) {}

    float  x() const { return x_; }
    float& x()       { return x_; }
};

int main()
{
    // initial value
    NonPOD initial(10.0f);

    //copying to pod wrapper
    PODWrapper<NonPOD> pod;
    std::memcpy(&pod, &initial, sizeof(NonPOD));

    // accessing pod wrapper
    NonPOD nonpod(*pod);
    std::cout << nonpod.x() << std::endl;

    return 0;
}

The use case is to be able to declare a struct of CUDA constant memory with any type (CUDA expects a POD type). Something like this:

__constant__ PODWrapper<NonPOD> constantData;

I tested this and it seem to work but I am especially concerned about memory issue, namely using memcpy to/from the 'this' pointer of the PODWrapper.

CodePudding user response:

Your PODWrapper exhibits undefined behaviour in three ways. Here's a fix:

template <class T>
struct PODWrapper 
{
    alignas(T) std::byte data_[sizeof(T)];

    const T& operator*()  const { return *std::launder(reinterpret_cast<const T*>(data_)); }
    const T* operator->() const { return  std::launder(reinterpret_cast<const T*>(data_)); }
};

Without aligning your byte store you are not guaranteed to have enough memory. Furthermore you must std::launder the memory address.

However, the biggest problem is that there is no object of type created anywhere (except for initial). The memory is there, but in terms of C no NonPOD object resides in that memory. You can use std::construct_at and std::destory_at to create and destroy the object.

std::construct_at(pod.data_, initial);

Note that the object is not the same as the memory where the object is stored. Especially for non-trivial types (the concept of POD is somewhat outdated and no longer applicable, btw.). See TrivialType for further information.

Do not memcopy into data_. It will not create an object and you will still be in UB-land.

The access looks fine to me.

CodePudding user response:

I'm not sure why you want to make wrapper when it is very simple to convert your "NonPOD" class to "POD" class.

Look at class properties here: https://en.cppreference.com/w/cpp/language/classes

You class lacks only 2 requierements:

  • each eligible copy constructor is trivial
  • has one or more eligible default constructors such that each is trivial.

To fix that, you need to remove non-trivial copy constructor (you can add default copy constructor if you want) and add default trivial constructor. I.e. this works:

#include <iostream>
#include <type_traits>

class NonPOD
{
    protected:

    float x_;

    public:

    // Add trivial constructor
    NonPOD() = default;
    NonPOD(float x) : x_(x) {}
    // Remove non-trivial copy constructor
    //NonPOD(const NonPOD& other) : x_(other.x_) {}

    float  x() const { return x_; }
    float& x()       { return x_; }
};

int main()
{
    std::cout << std::boolalpha;
    std::cout << std::is_trivially_copyable<NonPOD>::value << std::endl;
    std::cout << std::is_standard_layout<NonPOD>::value << std::endl;
    std::cout << std::is_trivial<NonPOD>::value << std::endl;
    std::cout << std::is_pod<NonPOD>::value << std::endl;
}

Output is:

true
true
true
true
  • Related