How can I define a wrapper for a referenced object (as in association, not composition) that
- is or behaves as
const
if the referenced object itself isconst
- is mutable if the referenced object is also mutable?
My concrete issue:
I am writing a function that internally deals with POD uint8_t[]
arrays, but is supposed to interface to the outside world with a wrapper class like
class BufferWrapper
{
public:
BufferWrapper(uint8_t* pau8, size_t ui) : m_pau8{pau8}, m_uiSize{ui} {}
uint8_t& operator[](size_t ui) { return m_pau8[ui]; }
const uint8_t& operator[](size_t ui) const { return m_pau8[ui]; }
size_t length() const { return m_uiSize; }
/* other convenience functions ... */
private:
uint8_t* m_pau8;
size_t m_uiSize;
};
I have written a convenience conversion template function from uint8_t[SIZE]
to BufferWrapper. (I know it only works for arrays where the size is known at compile time.)
template<typename T> BufferWrapper wrapArray(T& t)
{
return BufferWrapper(t, sizeof(t));
}
This works well as long as the arrays are mutable, but obviously fails to compile if the actual data source is a const uint8_t[]
as calling the BufferWrapper
constructor would cast away the const
ness of the source array.
What I would like to have is a const BufferWrapper
object that references a const uint8_t[]
, but which should not be implicitly changeable to non-const
.
I came up with code that compiles by overloading the function with a const T&
parameter type and using const_cast
inside.
template<typename T> const BufferWrapper wrapArray(const T& t)
{
return BufferWrapper(const_cast<T&>(t), sizeof(t));
}
However, this is a bad solution because the const
return type is dropped when copy-constructing another object, like in
BufferWrapper newObject = wrapArray(my_const_uint8_array);
which compiles, even though it should not.
I have found two different solutions for similar problems:
- How to deal with initialization of non-const reference member in const object? suggests to solve a similar problem by using multiple inheritance, but this sounds rather intricate, and in my case, some embedded compilers do not handle multiple inheritance very well.
- Why does C++ not have a const constructor? also addresses the issue of
const
disappearing, but this is some sort of a comprehension question and the solution is more of an explanation, and not a solution to the problem here.
Do you have any better solution?
Here is a self-contained working example
#include <cinttypes>
#include <cstddef>
#include <cstdio>
#include <cstring>
class BufferWrapper
{
public:
BufferWrapper(uint8_t* pau8, size_t ui) : m_pau8{pau8}, m_uiSize{ui} {}
void fill(uint8_t u8) { memset(m_pau8, u8, m_uiSize); }
uint8_t& operator[](size_t ui) { return m_pau8[ui]; }
const uint8_t& operator[](size_t ui) const { return m_pau8[ui]; }
size_t length() const { return m_uiSize; }
bool operator==(const BufferWrapper& rcco)
{
return (m_uiSize == rcco.m_uiSize) // size equality
&& (0 == memcmp(m_pau8, rcco.m_pau8, m_uiSize)); // and content equality
}
private:
uint8_t* m_pau8;
size_t m_uiSize;
};
// example for data consumer that accepts a BufferWrapper object
void readDataFromBuffer(const BufferWrapper& rcco)
{
for(size_t ui=0; ui<rcco.length(); ++ui)
{
printf("%02x", rcco[ui]);
}
printf("\n");
}
// convenience function to capture length of arrays
// (only works on arrays, not on pointers -- I know)
template<typename T> BufferWrapper wrapArray(T& t)
{
printf("BufferWrapper, ptr=%p, size=%zu\n", &t, sizeof(t));
return BufferWrapper(t, sizeof(t));
}
template<typename T> const BufferWrapper wrapArray(const T& t)
{
printf("const BufferWrapper, ptr=%p, size=%zu\n", &t, sizeof(t));
return BufferWrapper(const_cast<T&>(t), sizeof(t));
}
int main()
{
uint8_t au8[] = { 0xde, 0xad, 0xbe, 0xef };
constexpr uint8_t cau8[] = { 0xba, 0xaa, 0xad, 0xc0, 0xde };
readDataFromBuffer(wrapArray(au8));
readDataFromBuffer(wrapArray(cau8));
// this should _not_ compile, as it casts away the const of the Buffer object
BufferWrapper coFoo = wrapArray(cau8);
coFoo[0] = 0xde;
coFoo[1] = 0xee;
readDataFromBuffer(coFoo);
return 0;
}
Output (it can be seen that the contents of an actually const
variable was changed):
$ clang -Wall toy_example.cpp
$ ./a.out
BufferWrapper, ptr=0x7fff647544d4, size=4
deadbeef
const BufferWrapper, ptr=0x7fff647544cc, size=5
baaaadc0de
const BufferWrapper, ptr=0x7fff647544cc, size=5
deeeadc0de