How to write a wrapper around a C struct pointer that respects a constant

I am working in C ++, but I am using some C APIs that work by passing pointers to structures. In order to use data inside these structures in C ++ code, I write a C ++ class for each structure. for example if I have a C structure like

struct cStruct {
  int32_t data;
};

      

then I wrap it up with something like:

class CppWrapper {
  public:
    CppWrapper(cStruct* structData) : m_structData(structData) {}

    // These are the interface the user will deal with
    int data() const {return m_structData->data;}
    void setData(int data) {m_structData->data = data;}

  private:
    cStruct* m_structData = nullptr;
};

      

which can be used easily, for example

cStruct* s = some_c_function();
CppWrapper w(s);
w.setData(5);

      

However, if I have a C funtion that returns const cStruct*

, then I cannot use CppWrapper

and would have to make a separate wrapper that takes const cStruct*

as a constructor argument, saves, const cStruct*

and does not contain editing functions like setData()

.

Is it possible at all to write a wrapper that can adapt to constant or non-constant pointers and support that constant?

+3


source to share


3 answers


Original solution

The solution I ended up with was a wrapper template for the struct type so that I could have an instance of the template that only works with constant pointers. It looks something like this:

template<typename StructType = cStruct>
class Wrapper
{
  public:
    Wrapper(StructType* structData) : m_structData(structData) {}

    // These are the interface the user will deal with
    int data() const {return m_structData->data;}
    void setData(int data) {m_structData->data = data;}

  private:
    StructType* m_structData = nullptr;
};

      

I have given the template type default argument the structure type as both form documentation and reduce the type in some common cases.

This can be used in code by simply doing

cStruct* myCArray = some_c_function();
auto wrapper = WrapperType<>(myCArray)

const cStruct* myConstCArray = some_const_c_function();
auto constWrapper = WrapperType<const cStruct>(myConstCArray);

      

To help further, I have defined a function make_wrapper()

defined as

template <template <typename> class WrapperType, typename StructType>
WrapperType<StructType> make_wrapper(StructType* structData)
{
  return WrapperType<StructType>(structData);
}

      

so you can do

cStruct* myCArray = some_c_function();
auto wrapper = make_wrapper(myCArray)

const cStruct* myConstCArray = some_const_c_function();
auto constWrapper = make_wrapper(myConstCArray);

      

and therefore make_wrapper()

becomes a universal interface.

Based on this, the Wrapper interface can be used

wrapper.setData(42);
std::cout << wrapper.data() << std::endl;

      

and call

constWrapper.setData(42);

      



gives a corresponding error, eg assignment of member โ€˜cStruct::dataโ€™ in read-only object

.

Allow conversion to const

To convert Wrapper<cStruct>

to Wrapper<const cStruct>

(but not vice versa), you must introduce a set of constructors and operator=

overloads. These constructors should only be available in the version of the const

class.

This can be done std::enable_if

with std::is_const

and std::remove_const

. This is not the tidiest one as the constructor requires a dummy parameter, but it does the job.

These declarations must be placed in the class definition Wrapper

.

template <typename OtherT,
          typename = typename std::enable_if<std::is_same<const OtherT, StructType>::value>::type>
Wrapper(const Wrapper<OtherT>& nonConstOther)
  : m_structData(nonConstOther.m_structData) {}

template <typename Dummy = StructType,
          typename std::enable_if<std::is_const<Dummy>::value>::type>
Wrapper<StructType> operator=(const Wrapper<typename std::remove_const<StructType>::type>& nonConstOther)
  { m_structData = nonConstOther.m_structData; }
friend class Wrapper<typename std::add_const<StructType>::type>;

      

Thanks to Jonathan Wakely for hinting for a better version of the constructor using std::is_same

.

Reusable interface

It is possible that you need more than one of them in your C ++ interface to interact with many other C structures. So I think it would be helpful to decompose common code (especially this stuff with a lot of boilerplate) to reduce the boilerplate.

I ended up creating a base class observer_ptr_wrapper

(named similar to the suggested observer_ptr ) that contains the internals:

template<typename PointerType>
class observer_ptr_wrapper
{
public:
  observer_ptr_wrapper(PointerType* structData) : m_structData(structData) {}

  using NonConstPointerType = typename std::remove_const<PointerType>::type;
  friend class observer_ptr_wrapper<typename std::add_const<PointerType>::type>;

  template <typename OtherT,
            typename = typename std::enable_if<std::is_same<const OtherT, PointerType>::value>::type>
  observer_ptr_wrapper(const observer_ptr_wrapper<OtherT>& nonConstOther)
    : m_structData(nonConstOther.m_structData) {}

  template <typename Dummy = PointerType,
            typename std::enable_if<std::is_const<Dummy>::value>::type>
  observer_ptr_wrapper<PointerType> operator=(const observer_ptr_wrapper<typename std::remove_const<PointerType>::type>& nonConstOther)
    {m_structData = nonConstOther.m_structData;}

protected:
  PointerType* m_structData = nullptr;
};

      

which can then be inherited with:

template <typename StructType = const cStruct>
class cStructWrapper : public observer_ptr_wrapper<StructType>
{
public: 
  // These are the interface the user will deal with
  int data() const {return m_structData->data;}
  void setData(int data) {m_structData->data = data;}

//This is the boilerplate
public: using observer_ptr_wrapper<StructType>::observer_ptr_wrapper;
private: using observer_ptr_wrapper<StructType>::m_structData;
};

      

so that the overall pattern is reduced to only two lines.

0


source


you can create multiple constructors:



class CppWrapper {
  public:
    CppWrapper(cStruct* structData) : m_structData(structData), mc_structData(nullptr) {}
    CppWrapper(const cStruct* structData_c) : mc_structData(structData) {}
    // These are the interface the user will deal with
    int data() const {return m_structData->data;}
    void setData(int data) {m_structData->data = data;}

  private:
    cStruct* m_structData = nullptr;
    const cStruct* mc_structData;

};

      

0


source


How about starting with something simpler? The combination of templating specialization and inheritance goes a long way in providing what you want.

#include <iostream>

struct cStruct {
  int32_t data;
};

template <typename T> class CppWrapper;

// const version implements const member functions (getters)
template <>
class CppWrapper<const cStruct *> {
 public:
  CppWrapper(const cStruct * d) : d(d) {}

  int data() const { return d->data; }

 private:
  const cStruct * d;
};

// non-const version implements non-const member functions (setters)
template <>
class CppWrapper<cStruct *> : public CppWrapper<const cStruct *> {
 public:
  CppWrapper(cStruct * d) : CppWrapper<const cStruct *>(d), d(d) {}

  void setData(int data) { d->data = data; }

 private:
  cStruct * d;
};

int main(int argc, char * argv[]) {
  struct cStruct s;
  s.data = 0;

  CppWrapper<const cStruct *> a{&s};
  CppWrapper<cStruct *> b{&s};

  const CppWrapper<const cStruct *> c{&s};
  const CppWrapper<cStruct *> d{&s};

  // Getters...
  std::cout << "a.data() = " << c.data() << std::endl;
  std::cout << "b.data() = " << d.data() << std::endl;
  std::cout << "c.data() = " << c.data() << std::endl;
  std::cout << "d.data() = " << d.data() << std::endl;

  // Setters...
  std::cout << "Setting data to 1" << std::endl;
  // a.setData(1);  // Won't compile, as you would hope
  b.setData(1);
  // c.setData(1);  // Won't compile, as you would hope
  // d.setData(1);  // Won't compile, as you would hope

  // Getters (again)
  std::cout << "a.data() = " << c.data() << std::endl;
  std::cout << "b.data() = " << d.data() << std::endl;
  std::cout << "c.data() = " << c.data() << std::endl;
  std::cout << "d.data() = " << d.data() << std::endl;

  return 0;
}

      

The non-const version inherits all const get 'getter' functions and extends it with 'setters'. There aren't many code templates out there; perhaps not enough to warrant a more general solution?

0


source







All Articles