Propagate Constness to Data Pointed by Member Variables

Propagate constness to data pointed by member variables

  1. As @Alf P. Steinbach noted, you oversaw the fact that copying your pointer would yield a non-const object pointing to the same underlying object. Pimpl (below) nicely circumvent the issue by performing a deep-copy, unique_ptr circumvents it by being non-copyable. It is much easier, of course, if the pointee is owned by a single entity.

  2. Boost.Optional propagates const-ness, however it's not exactly a pointer (though it models the OptionalPointee concept). I know of no such other library.

  3. I would favor that they provide it by default. Adding another template parameter (traits class I guess) does not seem worth the trouble. However that would radically change the syntax from a classic pointer, so I am not sure that people would be ready to embrace it.


Code of the Pimpl class

template <class T>
class Pimpl
{
public:
/**
* Types
*/
typedef T value;
typedef const T const_value;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;

/**
* Gang of Four
*/
Pimpl() : _value(new T()) {}
explicit Pimpl(const_reference v) : _value(new T(v)) {}

Pimpl(const Pimpl& rhs) : _value(new T(*(rhs._value))) {}

Pimpl& operator=(const Pimpl& rhs)
{
Pimpl tmp(rhs);
swap(tmp);
return *this;
} // operator=

~Pimpl() { boost::checked_delete(_value); }

void swap(Pimpl& rhs)
{
pointer temp(rhs._value);
rhs._value = _value;
_value = temp;
} // swap

/**
* Data access
*/
pointer get() { return _value; }
const_pointer get() const { return _value; }

reference operator*() { return *_value; }
const_reference operator*() const { return *_value; }

pointer operator->() { return _value; }
const_pointer operator->() const { return _value; }

private:
pointer _value;
}; // class Pimpl<T>

// Swap
template <class T>
void swap(Pimpl<T>& lhs, Pimpl<T>& rhs) { lhs.swap(rhs); }

// Not to be used with pointers or references
template <class T> class Pimpl<T*> {};
template <class T> class Pimpl<T&> {};

How can I propagate const when returning a std::vector<int*> from a const method?

You're asking for std::experimental::propagate_const. But since it is an experimental feature, there is no guarantee that any specific toolchain is shipped with an implementation. You may consider implementing your own. There is an MIT licensed implementation, however. After including the header:

using namespace xpr=std::experimental;
///...
std::vector<xpr::propagate_const<int*>> my_ptr_vec;

Note however that raw pointer is considered evil so you may need to use std::unique_ptr or std::shared_ptr. propagate_const is supposed to accept smart pointers as well as raw pointer types.

Correct constness with pointer / object / template parameter

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;
}

Invoking a nonconst method on a member from a const method

When and object of type B is const, then all of its members are const, which means its two members are, for the duration of B::go(), effectively

A const a;
A * const aPtr;

The first is a constant object of type A, on which you can only call const member functions. The second, however, is a constant pointer to a non-constant A. You could not legally say aPtr = <anything> from within the function B::go(), since that would modify aPtr, which is constant.

A pointer to a constant A would be declared as A const* aPtr or const A* aPtr, which would then make calling the non-constant A::nonconst() illegal.

C++ - Why don't const functions force const-ness on member-pointers?

EDIT:

Regarding your code: you're never actually changing the member or returning a reference to it.

unsigned int& value2(void) const
{
*m_ptr = 10;
return *m_ptr;
//Why?
}
//returns the value your member points to, not your member

unsigned int* value3(void) const
{
return m_ptr; //Why?
}
//returns a copy of your member

What would be disallowed would be the version I posted in my snippet.

For example:

struct A
{
int * x;
int& getX() const { return *x; }
int* getX1() const { return x; }
int*& getX2() const { return x; } //error here
};

Only the last method will return an error, because:

  • first one returns the value, which is not const. The pointer is const.
  • second one returns the pointer by value, which again is ok
  • the third one actually returns the member by reference, that's why it's illegal


Related Topics



Leave a reply



Submit