Reputation: 7017
I have a template class foo (essentially a matrix). The template parameter for foo can only be int
, float
, or double
, i.e., not const int
. The reason for this is that I have specialized operators for these, and it seems redundant to duplicate the operators for the const cases. I have two get_data
functions, which return a pointer with appropriate constness. But I also wan't a row
function that selects a single row and returns a const foo
, such that the caller cannot modify the returned object.
My questions:
1) How can i make B const?
2) Should I make operator functions for e.g. foo ?
2) Should I make a reference_foo class?
template <class T>
class foo
{
using uint8_t = unsigned char;
int rows = 0;
int cols = 0;
T * data = nullptr;
bool reference = false;
public:
foo() = default;
//foo(const foo&) // this is not included here for simplicity
//foo& operator=(const foo&) // this is not included here for simplicity
foo(int r, int c) : rows(r), cols(c)
{
data = new T[rows * cols];
}
~foo()
{
if (!reference)
{
delete[] data;
}
}
T * get_data()
{
return data;
}
T const * get_data() const
{
return data;
}
const foo row(int r) const
{
foo t;
t.rows = 1;
t.cols = cols;
t.reference = true;
// t.data = get_data() + r * cols; // ERROR: invalid conversion from 'const uint8_t*' to 'uint8_t*'
t.data = const_cast<T*>(get_data()) + r * cols; // Not pretty, but "ok" if the returned object is const
return t;
}
};
int main()
{
const foo<int> A(2, 1);
// A.get_data()[0] = 1; // ERROR: assignment of read-only location, perfectly catched by compiler
auto B = A.row(1);
B.get_data()[0] = 1; // B is not const... overwritten...
return 0;
}
The operator functions has been left out for simplicity.
Upvotes: 0
Views: 175
Reputation: 69854
There are 2 kinds of constness here. Const data and const handle.
What we want to do is create sanity out of the four combinations:
Furthermore, marking a return value as const has no meaning. A return value is an r-value. It will be either copied or moved. This will not result in a const handle at the call site.
So we need to detect constness in 2 places in respect of get_data()
. C++ does the first for us with a const overload. Then we must defer to another template which is evaluated in deduced context so we can use std::enable_if
:
#include <cstddef>
#include <utility>
#include <type_traits>
// default getter - element != const element
template<class Element, typename = void>
struct data_getter
{
using element_type = Element;
using const_element_type = std::add_const_t<element_type>;
// detect mutable container
element_type* operator()(element_type ** pp) const
{
return *pp;
}
// detect const container
const_element_type* operator()(element_type * const * pp) const
{
return *pp;
}
};
// specific specialisation for element == const element
template<class Element>
struct data_getter<Element,
std::enable_if_t<
std::is_same<Element, std::add_const_t<Element>>::value>>
{
// in this case the container's constness is unimportant, so
// we use const because it means only writing one method
Element* operator()(Element *const* p) const
{
return *p;
}
};
template <class T>
class foo
{
public:
using element = T;
using const_element = std::add_const_t<element>;
int rows = 0;
int cols = 0;
element * data = nullptr;
bool reference = false;
public:
foo() = default;
//foo(const foo&) // this is not included here for simplicity
//foo& operator=(const foo&) // this is not included here for simplicity
foo(int r, int c) : rows(r), cols(c)
{
data = new element[rows * cols];
}
~foo()
{
if (!reference)
{
delete[] data;
}
}
decltype(auto) get_data()
{
// defer to getter
return data_getter<element>()(&data);
}
decltype(auto) get_data() const
{
// defer to getter
return data_getter<const_element>()(&data);
}
// this will return a mutable container of const data
foo<const_element> row(int r) const
{
foo<const_element> t;
t.rows = 1;
t.cols = cols;
t.reference = true;
t.data = get_data() + r * cols;
return t;
}
};
int main()
{
foo<int> A(2, 1);
A.get_data()[0] = 1;
auto AC = A.row(0);
auto x = AC.get_data()[0]; // fine
// AC.get_data()[0] = 1; // assignment of read-only location
return 0;
}
Upvotes: 1