Maybe it is a trivial question, but I can't actually find an answer. If I have a class like in the example below do I need to provide both const and non-const version of functions like in case of std::vector? Would a single constexpr function do the same job?
template <typename Type>
class Collection
{
public:
virtual ~Collection() {}
virtual size_t size() noexcept = 0;
virtual size_t size() const noexcept = 0;
virtual Type operator[](size_t index) noexcept = 0;
virtual Type operator[](size_t index) const noexcept = 0;
};
It depends. If the non-const version would do the same thing as the const version, then no. If someone has a non-const instance of a class, they can still call const methods.
There are some methods that may need both. Consider your indexing operator. If it instead returned a reference to the type, you would probably want both:
virtual Type& operator[](size_t index) noexcept = 0;
virtual const Type& operator[](size_t index) const noexcept = 0;
The methods aren't exactly the same because they are returning different types. This would mean that if someone has a const instance of your type, they could not get a mutable reference to an element.
A single constexpr function could do the job as long as the potential const/non-const implementations would be the same.
Related
I'm creating a type punning class View which takes a pointer to byte and adapts it to an array of T. The issue is that a non-const View can be constructed from a const byte*. I don't want to have separate, incompatible types like a View and ConstView. Maybe I could have a member bool readonly that gets set in the const byte* constructor and is checked for in the non-const operator[] overload, causing it to throw. Is there a better way to handle this?
using std::byte;
template <class T>
class View {
public:
typedef T __attribute__((may_alias)) value_type;
typedef value_type* pointer;
typedef const pointer const_pointer;
typedef value_type& reference;
typedef const reference const_reference;
View(byte* p)
: data { buffer }
{}
View(const byte* p)
: data { const_cast<byte*>(p) }
{}
reference operator[](int index) {
return reinterpret_cast<pointer>(data)[index];
}
const_reference operator[](int index) const {
return reinterpret_cast<const_pointer>(data)[index];
}
private:
byte* data;
};
We can actually do all of this checking at compile-time. You're using std::byte, so I assume you're on at least C++17, which means this is really straightforward (We can do a lot of these tricks with older C++ versions, but it involves more template trickery)
We can use static_assert to enable or disable functions depending on the input type. And we'll use is_const_v to check whether our T type is const or not.
template <class T>
class View {
public:
...
View(std::byte* p)
: data { p } {
static_assert(!std::is_const_v<T>);
}
View(const std::byte* p)
: data { const_cast<std::byte*>(p) } {
static_assert(std::is_const_v<T>);
}
reference operator[](int index) {
static_assert(!std::is_const_v<T>);
return reinterpret_cast<pointer>(data)[index];
}
const_reference operator[](int index) const {
return reinterpret_cast<const_pointer>(data)[index];
}
private:
std::byte* data;
};
static_assert is just like assert, except that it runs when the code is generated rather than when it's run. So we define two constructors. One takes an std::byte* and only exists when T is not constant. The other takes a const std::byte* and only exists when T is constant.
Likewise, we have two overloads for operator[]. The first overload returns a mutable reference but can only be used if T is non-const. The second returns a const reference can be used in general. We don't need any assertions for it. (The C++ standard library uses that idiom all over the place: One function returns a constant reference from a const this pointer and one returns a mutable reference, and C++'s overloading rules can handle it)
To use
View<int> x { new std::byte[1] };
View<const int> y { const_cast<const std::byte*>(new std::byte[1]) };
// All fine
x[0] = 100;
std::cout << x[0] << std::endl;
std::cout << y[0] << std::endl;
// Fails at compile time
// y[0] = 100;
return 0;
Also, you'll want to give Rule of Three/Five a thorough read at some point soon. You're taking a pointer as argument, so you need to understand how to manage that resource. You'll either need to (preferred) take a smart pointer rather than a raw one, or if you insist on the raw pointer then you need to write your own or delete the destructor, move and copy constructors, and move and copy assignment operators.
I have a custom class with two overloaded brackets operators -- setter and getter. As you know they look somewhat like this
class IntContainer {
public:
int const & operator[] (size_t i) const;
int & operator[] (size_t i);
}
The problem I'm facing now, is that I have to check when the value was set or when it was just accessed, that is I need to track all the changes in my container. It's hard since always only non const operator is called, for example
container[i] = 3; // Non const operator[] called
x = container[i]; // Again, non const operator[] called
In two cases above I need to differ inner behavior in container. So is there any way to explicitly call different operators in cases like above. I don't want to use const instance of container and to define another functions like set and get, though I'm looking for smoe right design pattern.
Thanks!
One trick is to create a proxy object. This lets you overload the assignment operator and put your tracking logic into there and then you can guarantee that any writes are captured. If you have
class Proxy
{
int& val;
Proxy(int& val) : val(val) {}
Proxy& operator=(int new_val)
{
// do tracking stuff
val = new_val;
}
operator int() { return val; }
};
then you can adjust IntContainer to
class IntContainer {
public:
int operator[] (size_t i) const;
Proxy operator[] (size_t i);
};
and now you'll call the tracking code when the user actually tries to assign into the reference.
I am working on some project and have problems with duplicating function code. Is there a way to declare only single signature of function which will work for both const and non-const params? Implementation of const and non-const functions are the same.
#include <iostream>
template <class Data>
struct Processor {
int process(const Data &data)
{
return 42;
}
int process(Data &data)
{
return 42;
}
};
int main() {
using data_type = int;
data_type non_const_data = 1;
const data_type const_data = 2;
std::cout << Processor<data_type>().process(non_const_data) << std::endl;
std::cout << Processor<data_type>().process(const_data) << std::endl;
return 0;
}
Example http://ideone.com/tv0TsF
THANKS FOR YOUR ANSWERS
UPDATE
And what about this example?
#include <iostream>
#include <vector>
template <class Container, class Function>
struct Invoker {
const Container& invoke(const Container &container, Function function)
{
for (auto &value : container) function(value);
}
Container& invoke(Container &container, Function function)
{
for (auto &value : container) function(value);
}
};
int main() {
std::vector<int> container {1, 2, 3};
auto fn = [](int val) {
std::cout << val << std::endl;
};
Invoker<decltype(container), decltype(fn)>().invoke(container, fn);
return 0;
}
http://ideone.com/KnyGdT
If implementation of both functions are same, then provide const parameter function only. It will work both for non-const and const object in your case.
However, if you want to keep the both the function, you can implement non-const parameter function in terms of const parameter function. It will help you to avoid duplicate code.
int process(const Data &data)
{
return 42;
}
int process(Data &data)
{
return process(static_cast<const Data&>data);
}
I'd suggest declaring only the most restrictive one, that being the one that expects a const param. Should there be an issue of sending a non-const pointer/reference into it, you can always make it const before calling the method.
Yes:
int process(const Data &data)
{
return 42;
}
This can be called with Data, Data &, and Data const &, and anything convertible to Data. A const reference can bind to a non-const object or reference.
It might not be exactly what you are looking for, but a similar situation arise in the case of const vs non-const member functions. Sometimes, say for instance in an access function, you would like to have two versions, one usable if the object is const and the other usable when the object is non-const.
struct MyString {
char & at(int index) {
if (index < size) {
return data[index];
else throw std::range_error("Index out of range");
}
char at(int index) const {
if (index < size) {
return data[index];
else throw std::range_error("Index out of range");
}
};
Now these functions look very similar, why can't I reuse one to implement the other?
The answer is you can, but it requires some very careful casting, first of all the general advice is to call the const-function from the non-const. As you can usually guarantee that calling a const function will be safe even from a non-const context. The call to the const-function will not actually change the object after all. (There are exceptions as with mutable variables, or the reverse of what I'm about to propose just now.)
The solution (according to More Effective C++), is use const cast to call the const version from the non-const:
const char & at(int index) const {
if (index < size) {
return data[index];
else throw std::range_error("Index out of range");
}
char & at(int index) {
return const_cast<char &>( static_cast<const MyString>(this)->at(index) );
}
This functions in two steps. First, cast this to a const pointer, that will allow us to invoke the const-version of at(). Then, once we return a const-reference to the element, cast it to a writeable reference.
I'm getting a compilation error when I call the constructor of TestComp, which is designed as follows:
template <typename R>
class IComparable
{
public:
virtual bool Equals(const R & rhs) const = 0;
};
class TestComp : IComparable<char*>
{
public:
std::string x;
TestComp(std::string & a)
{
x = a;
}
virtual bool Equals(const char* & a) const
{
return x == std::string(a);
}
};
Error:
error C2259: 'TestComp2' : cannot instantiate abstract class due to following members:'bool IComparable<R>::Equals(const R &) const' : is abstract with [ R=char * ]
which I can't understand as I'm defining TestComp::Equals with what appears to be the same signature as IComparable::Equals.
One thing I've noticed when trying various workarounds is that if I make both functions 'Equals(R & rhs) const' removing the const from the parameter then I no longer get this error.
Can someone help me understand this behavior?
The signature of the derived method is incorrect. It should be
virtual bool Equals(char* const& a) const
Note that the const is "applied" to the type before the next closest * and & and &&, i.e.,
const char* &, which is equivalent to char const* &, means a reference to a const-pointer to char.
char* const& means a const-reference to a pointer to char.
const R&, which is equivalent to R const&, means a const-reference to R.
const R &
That is a const reference (or, pedantically, a reference to a const object).
const char* & a
That is a non-const reference (to a pointer to a const object); therefore, it doesn't override the function taking a const reference. You need a const reference to a non-const pointer:
char * const & a
The const always qualifies the thing before it, unless it comes at the start, in which case it qualifies the first thing. Some people suggest making a habit of consistently putting it after the thing it qualifies, i.e. R const & rather than const R &, to slightly reduce the opportunity for confusion.
I have the following class, here is it full prototype:
class FlowEdge{
private:
const uint32_t from_;
const uint32_t to_;
const double capacity_;
double flow_;
public:
FlowEdge();
FlowEdge(uint32_t from, uint32_t to, double capacity);
uint32_t from() const;
uint32_t to() const;
uint32_t other(uint32_t vertex) const throw(std::invalid_argument);
double getCapacity() const;
double getFlow() const;
double residualCapacityTo(uint32_t vertex) const throw(std::invalid_argument);
void addResidualFlowTo(uint32_t vertex, double delta) throw(std::invalid_argument);
};
I use this class as std::deque element type: std::deque<FlowEdge> in another class. When I compile my project I receive an error said that my FlowEdge class have no available operator= method. This method is created by compiler by default, isn't it? What could be a problem? I haven't operator= nor in public, nor in protected section.
The compiler generates an operator= for you if it is able to do so. It's not able in your case, because you have a const member in the class. Such a member cannot be assigned to, so the default copy assignment operator wouldn't be well-defined. If you want to assign objects of this class, you have to provide a custom one, implementing it to preserve the semantics you want of the const member.
Of course, the easier alternative would be to make capacity_ a non-const double. Generally, const data members are only useful in very specific situations, and they're usually more trouble than they're worth.