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?
source to share
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.
source to share
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;
};
source to share
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?
source to share