Correct constness with pointer / object / template parameter - c++

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.

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:
const handle, const data = const
const handle, mutable data = const
mutable handle, const data = const
mutable handle, mutable data = mutable
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;
}

Related

Flexible array member replacement for constexpr context

I want an array of objects prefixed with size/capacity. My requirements are:
The array elements should to be constructed on demand, like std::vector.
This object itself will be shared (i.e., heap allocated), so using std::vector instead would imply 2 levels of indirection and 2 allocations, which I have to avoid.
Requirements up to this point are non-negotiable. Here's my sample to get the rough idea of what I'd do:
#include <cassert>
#include <cstdio>
#include <string>
template <class T>
struct Array {
private:
int size_;
int capacity_;
alignas(T) unsigned char data_[];
Array() = default;
public:
Array(Array const&) = delete;
Array& operator=(Array const&) = delete;
static auto newArr(int capacity) {
auto p = new unsigned char[sizeof(Array) + capacity * sizeof(T)];
auto pObj = new (p) Array;
pObj->size_ = 0;
pObj->capacity_ = capacity;
return pObj;
}
static auto deleteArr(Array* arr) {
if (!arr) return;
for (int i = 0; i != arr->size_; ++i) arr->get(i).~T();
arr->~Array();
delete[] reinterpret_cast<unsigned char*>(arr);
}
auto& get(int index) {
return reinterpret_cast<T&>(data_[index * sizeof(T)]);
}
auto push_back(T const& t) {
assert(size_ < capacity_);
new (&get(size_++)) T(t);
}
};
int main() {
auto arr = Array<std::string>::newArr(5);
for (int i = 0; i != 3; ++i) {
arr->push_back(std::to_string(i));
}
for (int i = 0; i != 3; ++i) {
std::printf("arr[%d] = %s\n", i, arr->get(i).c_str());
}
Array<std::string>::deleteArr(arr);
}
It uses a flexible-array-member, which is an extension and that's OK by me (works in GCC and clang? then it's OK). But it is not constexpr friendly because it necessarily uses:
placement new, not allowed in constexpr context for some reason, even though that's surely what allocators do. We can't replace it with an allocator because they don't support the flexible array member trick.
reinterpret_cast to access the elements as T and to free the memory at the end.
My question:
How do I satisfy the previously mentioned requirements and keep the class constexpr friendly?
Ultimately, what you're trying to do is create a contiguous series of objects in unformed memory that isn't defined by a single, valid C++ struct or by a C++ array of Ts. Constexpr allocations cannot do that.
You can allocate a byte array in constexpr code. But you cannot subsequently do any of the casting that normal C++ would require in order to partition this memory into a series of objects of different types. You can allocate storage suitable for an array of Array<T> objects. Or you can allocate storage suitable for an array of T objects. But std::allocator<T>::allocate will always return a T*. And constexpr code doesn't let you cast this pointer to some other, unrelated type.
And without being able to do this cast, you cannot later call std::construct_at<T>, since the template parameter T must match the pointer type you give it.
This is of course by design. Every constexpr allocation of type T must contain zero or more Ts. That's all it can contain.
Considering #NicolBolas's answer, this can't be done as is, but if you can afford an indirection, it can be done. You do separate allocations if the object is constructed at compile-time where performance concern doesn't exist, and do the single-allocation + reinterpret_cast trick if constructed at runtime:
#include <cassert>
#include <new>
#include <type_traits>
#include <memory>
template <class T>
struct ArrayData {
protected:
int size_ = 0;
int capacity_ = 0;
T *buffer;
};
template <class T>
struct alignas(std::max(alignof(T), alignof(ArrayData<T>))) Array
: ArrayData<T> {
private:
constexpr Array() = default;
using alloc = std::allocator<T>;
using alloc_traits = std::allocator_traits<alloc>;
public:
Array(Array const &) = delete;
Array &operator=(Array const &) = delete;
constexpr static auto newArr(int capacity) {
if (std::is_constant_evaluated()) {
auto arr = new Array<T>();
alloc a;
T *buffer = alloc_traits::allocate(a, capacity);
arr->capacity_ = capacity;
arr->buffer = buffer;
return arr;
} else {
auto p = new unsigned char[sizeof(Array) + capacity * sizeof(T)];
auto pObj = new (p) Array;
pObj->capacity_ = capacity;
pObj->buffer = std::launder(reinterpret_cast<T *>(pObj + 1));
return pObj;
}
}
constexpr static auto deleteArr(Array *arr) noexcept {
if (!arr) return;
auto p = arr->buffer;
for (int i = 0, size = arr->size_; i != size; ++i)
std::destroy_at(p + i);
if (std::is_constant_evaluated()) {
auto capacity = arr->capacity_;
delete arr;
alloc a;
alloc_traits::deallocate(a, p, capacity);
} else {
arr->~Array();
delete[] reinterpret_cast<unsigned char *>(arr);
}
}
constexpr auto &get(int index) { return this->buffer[index]; }
constexpr auto push_back(T const &t) {
assert(this->size_ < this->capacity_);
std::construct_at(this->buffer + this->size_++, t);
}
};
constexpr int test() {
auto const size = 10;
auto arr = Array<int>::newArr(size);
for (int i = 0; i != size; ++i) arr->push_back(i);
int sum = 0;
for (int i = 0; i != size; ++i) sum += arr->get(i);
Array<int>::deleteArr(arr);
return sum;
}
int main() {
int rt = test();
int constexpr ct = test();
return rt == ct;
}
Here's another approach. I've used unions to make it possible for subobjects of a single array object to have different types and different lifetimes. I'm not entirely sure if everything's strictly legal, but the major compilers don't complain.
I did need to have newArr return an object rather than pointer. If that's not an issue, it would probably make more sense to just give FlexArray an actual constructor and destructor.
#include <memory>
#include <cassert>
template <typename T>
class FlexArray
{
public:
FlexArray(const FlexArray&) = delete;
FlexArray& operator=(const FlexArray&) = delete;
static constexpr FlexArray newArr(unsigned int capacity);
constexpr void deleteArr();
constexpr unsigned int size() const;
constexpr unsigned int capacity() const;
constexpr T& get(unsigned int index);
constexpr void push_back(T const& obj);
private:
struct Header {
unsigned int size;
unsigned int capacity;
};
static constexpr auto T_per_node =
(sizeof(T) < sizeof(Header)) ? sizeof(Header)/sizeof(T) : 1U;
union MaybeT {
unsigned char dummy;
T obj;
constexpr MaybeT() {}
explicit constexpr MaybeT(const T& src) : obj(src) {}
constexpr ~MaybeT() {}
};
union U {
Header head;
MaybeT data[T_per_node];
constexpr U() : data{} {}
constexpr ~U() {}
};
U* nodes;
explicit constexpr FlexArray(U* n) : nodes(n) {}
};
template <typename T>
constexpr FlexArray<T> FlexArray<T>::newArr(unsigned int capacity)
{
auto new_nodes = new U[1 + (capacity + (T_per_node-1))/T_per_node];
new_nodes[0].head = {0, capacity};
return FlexArray{new_nodes};
}
template <typename T>
constexpr void FlexArray<T>::deleteArr()
{
unsigned int i = size();
while (i--) {
get(i).~T();
}
delete[] nodes;
nodes = nullptr;
}
template <typename T>
constexpr unsigned int FlexArray<T>::size() const
{
return nodes[0].head.size;
}
template <typename T>
constexpr unsigned int FlexArray<T>::capacity() const
{
return nodes[0].head.capacity;
}
template <typename T>
constexpr T& FlexArray<T>::get(unsigned int index)
{
return nodes[1 + index / T_per_node].data[index % T_per_node].obj;
}
template <typename T>
constexpr void FlexArray<T>::push_back(const T& obj)
{
assert(size() < capacity());
auto index = nodes[0].head.size++;
MaybeT *addr = nodes[1 + index / T_per_node].data + (index % T_per_node);
addr->~MaybeT();
std::construct_at(addr, obj);
}
#include <functional>
constexpr int test()
{
int a = -1;
int b = 2;
int c = 5;
auto arr = FlexArray<std::reference_wrapper<int>>::newArr(3);
arr.push_back(std::ref(a));
arr.push_back(std::ref(b));
arr.push_back(std::ref(c));
arr.get(1).get() = 7;
auto sum = arr.get(0) + b + arr.get(2);
arr.deleteArr();
return sum;
}
static_assert(test() == 11);
See it on godbolt.

Const and non-const c++ constructor

Is there a way in C++03 (or earlier) to write a class that can either store a const or non-const pointer, and handles access appropriately? Take the usage of the non-functional "SometimesConst" class as an example:
class SometimesConst
{
public:
SometimesConst(int * buffer) : buffer(buffer) {} // Needs const qualifier?
int* get() { return buffer; } // Needs const qualifier?
void increment() { counter++; }
private:
int * buffer; // Needs const qualifier?
int counter;
};
void function(int * n, const int * c)
{
// These are both okay
SometimesConst wn(n);
SometimesConst wc(c);
// Reading the value is always allowed
printf("%d %d", wn.get()[0], wc.get()[0]);
// Can increment either object's counter
wn.increment();
wc.increment();
// Can set non-const pointer
wn.get()[0] = 5;
// Should generate a compiler error
wc.get()[0] = 5;
}
Creating a const SometimesConst would not allow modification of the counter property of the object. Can a class be designed that has compile-time const safety for input objects, only if they are passed in as const?
No, not the way you are wanting to use it. The only way to have different behavior at compile time is to have different types. However, you can make that fairly easy to use:
#include <stdio.h>
template <typename T>
class SometimesConst
{
public:
SometimesConst(T* buffer) : buffer(buffer) { }
T* get() { return buffer; }
void increment() { ++counter; }
private:
T *buffer;
int counter;
};
typedef SometimesConst<const int> IsConst;
typedef SometimesConst<int> IsNotConst;
void function(int * n, const int * c)
{
IsNotConst wn(n);
IsConst wc(c);
// Reading the value is always allowed
printf("%d %d", wn.get()[0], wc.get()[0]);
// Can increment either object's counter
wn.increment();
wc.increment();
// Can set non-const pointer
wn.get()[0] = 5;
// Should generate a compiler error
wc.get()[0] = 5;
}
The language already mostly lets you do this with a simple class; with the way const cascades to access to members (combined with mutable for the counter member, which you've indicated should always be mutable), you can provide both read-only and read-write access to a buffer quite easily:
class C
{
public:
C(int* buffer) : buffer(buffer) {}
const int* get() const { return buffer; }
int* get() { return buffer; }
void increment() const { counter++; }
private:
int* buffer;
mutable int counter;
};
void function(int* n)
{
// These are both okay
C wn(n);
const C wc(n);
// Reading the value is always allowed
printf("%d %d", wn.get()[0], wc.get()[0]);
// Can increment either object's counter
wn.increment();
wc.increment();
// Can set non-const pointer
wn.get()[0] = 5;
// Generates a compiler error
wc.get()[0] = 5;
}
What you can't do with this is neatly arrange for the class to be instantiated with either a int* or a const int*; the two lead to totally different semantics for your class, so you should split it into two if you really need that.
Fortunately, templates make this easy:
template <typename T>
class C
{
public:
C(T* buffer) : buffer(buffer) {}
const T* get() const { return buffer; }
T* get() { return buffer; }
void increment() const { counter++; }
private:
T* buffer;
mutable int counter;
};
Now a C<int> is as above, but a C<const int> only provides read-only access to the buffer, even when the C<const int> object itself is not marked as const:
void function(int* n1, const int* n2)
{
C<int> a(n1);
C<const int> b(n2);
const C<int> c(n1);
const C<const int> d(n2);
// Reading the value is always allowed
printf("%d %d %d %d",
a.get()[0], b.get()[0],
c.get()[0], d.get()[0]
);
// Incrementing the counter is always allowed
a.increment();
b.increment();
c.increment();
d.increment();
// Can set non-const pointer
a.get()[0] = 5;
// Cannot set const pointer, or const/non-const pointer behind const object
//b.get()[0] = 5;
//c.get()[0] = 5;
//d.get()[0] = 5;
}
Live demo
I think that there is a design problem if you want to store two different things which must be handled in different ways in one class. But yes, you can do it:
struct X{};
class A
{
public:
A(const X*) { cout << "const" << endl; }
A(X*) { cout << "non const" << endl; }
};
int main()
{
const X x1;
X x2;
A a1(&x1);
A a2(&x2);
}
the output is expected:
const
non const

overload operator[] on return type

There is something that is troubling my brain since a moment: I am trying to overload the [] operator based on the return type. Here is what I need to do:
class A {
private:
double* data_;
int N_;
public:
A (N=0):N_(N){
data_ = new double[N];
}
~A {delete[] data_;}
double operator[] (const int i) {
return data_[i];
}
double* operator[] (const int i) {
return &data[i]; // for example; in fact here i need to return some block of data_
}
};
This code won't compile; and that is my problem. Can someone help me to solve this problem?
PS: I know how to overload normal functions on the return type for example:
int foo ();
string foo ();
I used some tricks that I read in this forum. In this way:
struct func {
operator string() { return "1";}
operator int() { return 2; }
};
int main( ) {
int x = func(); // calls int version
string y = func(); // calls string version
double d = func(); // calls int version
cout << func() << endl; // calls int version
func(); // calls neither
}
Thank you.
Two method overloads must have different signatures. The return type is not part of the signature of a method.
You can use the same "trick" that you use for functions, that is use a proxy object with conversion operators:
class A
{
private:
double* data_;
int N_;
public:
A (int N = 0)
: N_(N), data_(new double[N])
{}
~A() { delete[] data_; }
struct proxy
{
int i;
double * data;
operator double() const
{
return data[i];
}
operator double*()
{
return &data[i];
}
operator double const *() const
{
return &data[i];
}
};
proxy operator[] (int const i) {
proxy p { i, data_ };
return p;
}
proxy const operator[] (int const i) const {
proxy p { i, data_ };
return p;
}
};
int main()
{
{
A a(12);
double d = a[0];
double * pd = a[0];
}
{
A const ca(12);
double d = ca[0];
//double * pd = ca[0]; // does not compile thanks to overloads on const
double const * pcd = ca[0];
}
}
However, I would argue that this is a terrible idea. Having your operator[] return either a value or a pointer to this value is guaranteed to confuse the users of your class, in addition to making it impractical to use in expressions where both types are possible. For instance, std::cout << a[0]; would not compile (ambiguous overloads).
Probably you need something like that:
class A {
private:
double* data_;
int N_;
... // other stuff
public:
double operator[] (const int i) const { // note const here
return data_[i];
}
double& operator[] (const int i) { // note reference here
return data_[i];
}
};
also operator should be public to have a sense.

Cannot assign int to member int of returned class

Not 100% sure whether my question is worded correctly as I don't fully understand my problem.
For my course I need to create my own smart pointer to clean up after itself.
Here's my code so far:
Header:
class Test
{
public:
Test()
{
m_iTest1 = 4;
m_iTest2 = 3;
m_iTest3 = 2;
m_iTest4 = 1;
}
Test (int a, int b, int c, int d)
{
m_iTest1 = a;
m_iTest2 = b;
m_iTest3 = c;
m_iTest4 = d;
}
Test(const Test& a_oTest)
{
m_iTest1 = a_oTest.m_iTest1;
m_iTest2 = a_oTest.m_iTest2;
m_iTest3 = a_oTest.m_iTest3;
m_iTest4 = a_oTest.m_iTest4;
}
~Test(){;}
int m_iTest1;
int m_iTest2;
int m_iTest3;
int m_iTest4;
};
template<class T>
class SmartData
{
public:
template<class T> friend class SmartPointer;
SmartData();
SmartData(const T& a_oData);
~SmartData();
T operator * () const;
unsigned int GetCount(){return m_uiCount;}
protected:
void IncrementCount(){++m_uiCount;}
void DecrementCount();
void DeleteThis();
unsigned int m_uiCount;
T* m_poData;
};
template<class T>
class SmartPointer
{
public:
SmartPointer();
SmartPointer(SmartData<T>& a_oSmartData);
SmartPointer(const SmartPointer& a_oSmartPointer);
~SmartPointer();
SmartPointer<T>& operator = (const SmartPointer<T>& a_oSmartPointer);
T operator *() const;
SmartData<T>* operator ->() const;
unsigned int GetCount() const;
private:
SmartData<T>* m_poSmartData;
};
#include "smartpointer.inl"
Inline file:
template<class T>
SmartData<T>::SmartData()
{
m_uiCount = 0;
m_poData = new T();
}
template<class T>
SmartData<T>::SmartData(const T& a_oData)
{
m_uiCount = 0;
m_poData = new T(a_oData);
}
template<class T>
SmartData<T>::~SmartData()
{
if (m_poData)
{
delete m_poData;
}
}
template<class T>
T SmartData<T>::operator * () const
{
return *m_poData;
}
template<class T>
void SmartData<T>::DecrementCount()
{
if (m_uiCount - 1 == 0 || m_uiCount == 0)
{
DeleteThis();
return;
}
--m_uiCount;
}
template<class T>
void SmartData<T>::DeleteThis()
{
if (m_poData)
{
delete m_poData;
m_poData = 0;
}
}
template<class T>
SmartPointer<T>::SmartPointer()
{
m_poSmartData = new SmartData<T>();
m_poSmartData->IncrementCount();
}
template<class T>
SmartPointer<T>::SmartPointer(SmartData<T>& a_oSmartData)
{
m_poSmartData = &a_oSmartData;
m_poSmartData->IncrementCount();
}
template<class T>
SmartPointer<T>::SmartPointer(const SmartPointer& a_oSmartPointer)
{
m_poSmartData = a_oSmartPointer.a_oSmartData;
m_poSmartData->IncrementCount();
}
template<class T>
SmartPointer<T>::~SmartPointer()
{
m_poSmartData->DecrementCount();
m_poSmartData = 0;
}
template<class T>
SmartPointer<T>& SmartPointer<T>::operator = (const SmartPointer<T>& a_oSmartPointer)
{
m_poSmartData = a_oSmartPointer.m_poSmartData;
m_poSmartData->IncrementCount();
}
template<class T>
T SmartPointer<T>::operator *() const
{
return *m_poSmartData->m_poData;
}
template<class T>
SmartData<T>* SmartPointer<T>::operator ->() const
{
return m_poSmartData;
}
template<class T>
unsigned int SmartPointer<T>::GetCount() const
{
return m_poSmartData->m_uiCount;
}
main.cpp
void SomeFunction1(SmartData<Test>& a_SmartData)
{
SmartPointer<Test> oSmartPointer2(a_SmartData);
}
void main()
{
SmartData<int> oSmartData1(5);
if (1)
{
SmartPointer<int> oSmartPointer1(oSmartData1);
int iTemp1 = oSmartPointer1->GetCount();
int iTemp2 = *oSmartPointer1;
int iTemp3 = *oSmartData1;
}
if (1)
{
SmartData<int> oSmartData2(5);
}
SmartData<Test> oSmartData3;
(*oSmartData3).m_iTest1 = 5; //Does not work
if (1)
{
SmartData<Test> oSmartData4(oSmartData3);
SomeFunction1(oSmartData3);
//oSmartData4 still exits
}
}
Everything works fine, the data is cleaned up after itself and I get no leaks... except for one line:
(*oSmartData3).m_iTest1 = 5;
I'm compiling with visual studio, and when I place the "." after "(*oSmartData3)"... "m_iTest1" comes up correctly. Except I get an error:
error C2106: '=' : left operand must be l-value
I'm not sure why this doesn't work or what to change so it does work.
Look closer at the declaration of operator*() in SmartData:
T operator * () const;
This means that this operator is returning an object of type T, which is a copy of m_poSmartData->m_poData. It is a temporary object in this context:
(*oSmartData3).m_iTest1 = 5; //Does not work
Of course, you cannot assign a value to a temporary object, because it is not an l-value. Read more about what l-values and r-values are here: http://publib.boulder.ibm.com/infocenter/comphelp/v7v91/index.jsp?topic=%2Fcom.ibm.vacpp7a.doc%2Flanguage%2Fref%2Fclrc05lvalue.htm
I would suggest that you return a reference to m_poSmartData->m_poData
in operator*() (if I'm understanding correctly what you are trying to do).
Your T operator *() const is returning a temporary object (i.e. a copy), which is not an l-value (cannot be assigned to). Return a reference instead:
T& operator *() const;
Does this work:
oSmartData3.m_iTest1 = 5;

Member pointer to array element

It's possible to define a pointer to a member and using this later on:
struct foo
{
int a;
int b[2];
};
int main()
{
foo bar;
int foo::* aptr=&foo::a;
bar.a=1;
std::cout << bar.*aptr << std::endl;
}
Now I need to have a pointer to a specific element of an array, so normally I'd write
int foo::* bptr=&(foo::b[0]);
However, the compiler just complains about an "invalid use of non-static data member 'foo::b'"
Is it possible to do this at all (or at least without unions)?
Edit: I need a pointer to a specific element of an array, so int foo::* ptr points to the second element of the array (foo::b[1]).
Yet another edit: I need to access the element in the array by bar.*ptr=2, as the pointer gets used somewhere else, so it can't be called with bar.*ptr[1]=2 or *ptr=2.
However, the compiler just complains about an "invalid use of non-static data member 'foo::b'"
This is because foo::a and foo::b have different types. More specifically, foo::b is an array of size 2 of ints. Your pointer declaration has to be compatible i.e:
int (foo::*aptr)[2]=&foo::b;
Is it possible to do this at all (or at least without unions)?
Yes, see below:
struct foo
{
int a;
int b[2];
};
int main()
{
foo bar;
int (foo::*aptr)[2]=&foo::b;
/* this is a plain int pointer */
int *bptr=&((bar.*aptr)[1]);
bar.a=1;
bar.b[0] = 2;
bar.b[1] = 11;
std::cout << (bar.*aptr)[1] << std::endl;
std::cout << *bptr << std::endl;
}
Updated post with OP's requirements.
The problem is that, accessing an item in an array is another level of indirection from accessing a plain int. If that array was a pointer instead you wouldn't expect to be able to access the int through a member pointer.
struct foo
{
int a;
int *b;
};
int main()
{
foo bar;
int foo::* aptr=&(*foo::b); // You can't do this either!
bar.a=1;
std::cout << bar.*aptr << std::endl;
}
What you can do is define member functions that return the int you want:
struct foo
{
int a;
int *b;
int c[2];
int &GetA() { return a; } // changed to return references so you can modify the values
int &Getb() { return *b; }
template <int index>
int &GetC() { return c[index]; }
};
typedef long &(Test::*IntAccessor)();
void SetValue(foo &f, IntAccessor ptr, int newValue)
{
cout << "Value before: " << f.*ptr();
f.*ptr() = newValue;
cout << "Value after: " << f.*ptr();
}
int main()
{
IntAccessor aptr=&foo::GetA;
IntAccessor bptr=&foo::GetB;
IntAccessor cptr=&foo::GetC<1>;
int local;
foo bar;
bar.a=1;
bar.b = &local;
bar.c[1] = 2;
SetValue(bar, aptr, 2);
SetValue(bar, bptr, 3);
SetValue(bar, cptr, 4);
SetValue(bar, &foo::GetC<0>, 5);
}
Then you at least have a consistent interface to allow you to change different values for foo.
2020 update, with actual solution:
The Standard does currently not specify any way to actually work with the member pointers in a way that would allow arithmetics or anything to get the pointer to the "inner" array element
OTOH, the standard library now has all the necessities to patch the appropriate member pointer class yourself, even with the array element access.
First, the member pointers are usually implemented as "just offsets", although quite scary. Let's see an example (on g++9, arch amd64):
struct S { int a; float b[10]; };
float(S::*mptr)[10] = &S::b;
*reinterpret_cast<uintptr_t *>(&mptr) //this is 4
int S::*iptr = &S::a;
*reinterpret_cast<uintptr_t *>(&iptr) //this is 0
iptr = nullptr;
*reinterpret_cast<uintptr_t *>(&iptr) //this seems to be 18446744073709551615 on my box
Instead you can make a bit of a wrapper (it's quite long but I didn't want to remove the convenience operators):
#include <type_traits>
template<class M, typename T>
class member_ptr
{
size_t off_;
public:
member_ptr() : off_(0) {}
member_ptr(size_t offset) : off_(offset) {}
/* member access */
friend const T& operator->*(const M* a, const member_ptr<M, T>& p)
{ return (*a)->*p; }
friend T& operator->*(M* a, const member_ptr<M, T>& p)
{ return (*a)->*p; }
/* operator.* cannot be overloaded, so just take the arrow again */
friend const T& operator->*(const M& a, const member_ptr<M, T>& p)
{ return *reinterpret_cast<const T*>(reinterpret_cast<const char*>(&a) + p.off_); }
friend T& operator->*(M& a, const member_ptr<M, T>& p)
{ return *reinterpret_cast<T*>(reinterpret_cast<char*>(&a) + p.off_); }
/* convert array access to array element access */
member_ptr<M, typename std::remove_extent<T>::type> operator*() const
{ return member_ptr<M, typename std::remove_extent<T>::type>(off_); }
/* the same with offset right away */
member_ptr<M, typename std::remove_extent<T>::type> operator[](size_t offset) const
{ return member_ptr<M, typename std::remove_extent<T>::type>(off_)+offset; }
/* some operators */
member_ptr& operator++()
{ off_ += sizeof(T); return *this; };
member_ptr& operator--()
{ off_ -= sizeof(T); return *this; };
member_ptr operator++(int)
{ member_ptr copy; off_ += sizeof(T); return copy; };
member_ptr operator--(int)
{ member_ptr copy; off_ -= sizeof(T); return copy; };
member_ptr& operator+=(size_t offset)
{ off_ += offset * sizeof(T); return *this; }
member_ptr& operator-=(size_t offset)
{ off_ -= offset * sizeof(T); return *this; }
member_ptr operator+(size_t offset) const
{ auto copy = *this; copy += offset; return copy; }
member_ptr operator-(size_t offset) const
{ auto copy = *this; copy -= offset; return copy; }
size_t offset() const { return off_; }
};
template<class M, typename T>
member_ptr<M, T> make_member_ptr(T M::*a)
{ return member_ptr<M, T>(reinterpret_cast<uintptr_t>(&(((M*)nullptr)->*a)));}
Now we can make the pointer to the array element directly:
auto mp = make_member_ptr(&S::b)[2];
S s;
s->*mp = 123.4;
// s.b[2] is now expectably 123.4
Finally, if you really, really like materialized references, you may get a bit haskell-lensish and make them compose:
// in class member_ptr, note transitivity of types M -> T -> TT:
template<class TT>
member_ptr<M,TT> operator+(const member_ptr<T,TT>&t)
{ return member_ptr<M,TT>(off_ + t.offset()); }
// test:
struct A { int a; };
struct B { A arr[10]; };
B x;
auto p = make_member_ptr(&B::arr)[5] + make_member_ptr(&A::a)
x->*p = 432.1;
// x.arr[5].a is now expectably 432.1
typedef int (foo::*b_member_ptr)[2];
b_member_ptr c= &foo::b;
all works.
small trick for member and function pointers usage.
try to write
char c = &foo::b; // or any other function or member pointer
and in compiller error you will see expected type, for your case int (foo::*)[2].
EDIT
I'm not sure that what you want is legal without this pointer. For add 1 offset to your pointer you should get pointer on array from your pointer on member array. But you can dereference member pointer without this.
You can't do that out of the language itself. But you can with boost. Bind a functor to some element of that array and assign it to a boost::function:
#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>
#include <boost/function.hpp>
#include <iostream>
struct test {
int array[3];
};
int main() {
namespace lmb = boost::lambda;
// create functor that returns test::array[1]
boost::function<int&(test&)> f;
f = lmb::bind(&test::array, lmb::_1)[1];
test t = {{ 11, 22, 33 }};
std::cout << f(t) << std::endl; // 22
f(t) = 44;
std::cout << t.array[1] << std::endl; // 44
}
I'm not sure if this will work for you or not, but I wanted to do a similar thing and got around it by approaching the problem from another direction. In my class I had several objects that I wanted to be accessible via a named identifier or iterated over in a loop. Instead of creating member pointers to the objects somewhere in the array, I simply declared all of the objects individually and created a static array of member pointers to the objects.
Like so:
struct obj
{
int somestuff;
double someotherstuff;
};
class foo
{
public:
obj apples;
obj bananas;
obj oranges;
static obj foo::* fruit[3];
void bar();
};
obj foo::* foo::fruit[3] = { &foo::apples, &foo::bananas, &foo::oranges };
void foo::bar()
{
apples.somestuff = 0;
(this->*(fruit[0])).somestuff = 5;
if( apples.somestuff != 5 )
{
// fail!
}
else
{
// success!
}
}
int main()
{
foo blee;
blee.bar();
return 0;
}
It seems to work for me. I hope that helps.