1

I am currently working on a program in which I encountered an issue (not for the first time) where I have two acessor methods for a data structure. The methods are identical, except one is const and returns a const pointer while the other is non-const and returns a non-const pointer. The code in the methods is non-trivial, making it ideal to not duplicate it between the two methods for all the same reasons that code duplication is bad in general. What is the best way to solve this issue?

The only reasonable-seeming idea that I have is this, but I am not entirely convinced that this is best because 1) I am not sure if a const_cast is acceptable here with regard to undefined behavior and other violations of the C++ standard¹, and 2) I have heard that a const_cast is a sign of bad code.

Node *NodeSet::getNode(int index){
    const Node *node = static_cast<const NodeSet*>(this)->getNode(index);
    return const_cast<Node*>(node);
}

const Node *NodeSet::getNode(int index) const{
    //non-trivial code to get the correct node
}

¹: It is important to me that I write standard C++ that does not rely on any particular compiler to compile and run correctly.

2

1 Answer 1

2

You are right that a const cast can be problematic. The issue is that by changing the constness, other overloads might be selected so that the exact same code might end up behaving differently. The issue is the cast from Node const* to Node*: maybe the Node would be borrowed in the const case, but might be a new object in the non-const case.

A similar case in the standard library is the type of accessing an element of a vector<bool>: the const case can simply return a value, the non-const case must return a proxy object that behaves like a reference.

If none of these issues apply in your case, the const cast is safe and is the idiomatic solution. Use it!

Otherwise, we want to write the same code once but have it compiled separately for the const and non-const case. To do this, we declare both methods as usual, and have them call a free function that uses a template to cover both the const and non-const case. Something like:

// header

struct NodeSet {
  auto getNode(int index) -> Node*;
  auto getNode(int index) const -> Node const*;
};

// implementation

template<class Self>  // could be "NodeSet" or "NodeSet const"
static auto getNode(Self& nodeset, int index) {
  ... // complex logic
  // use return type deduction for convenience,
  // otherwise you could do something like "-> decltype(nodeset.getNode(0))"
}

auto NodeSet::getNode(int index) -> Node* {
  return getNode(*this, index);
}

auto NodeSet::getNode(int index) const -> Node const* {
  return getNode(*this, index);
}
3
  • 1
    You might want to add that when using const_cast for providing both overloads needed for const-correctness, one must refrain from casting away the constness of a truely constant object. Commented Mar 18, 2019 at 21:13
  • @Deduplicator Yours is not the first comment that I saw that was talking about "truly constant object"s. What those are is unclear to me, however, as every object is going to be stored in RAM that the program has access to, and thus can change (unless a "truly constant object" refers only to something like a pointer given from an OS api into write-protected memory, in a very system dependent manner).
    – john01dav
    Commented Mar 18, 2019 at 21:22
  • @john01dav The point is whether the C++ abstract machine considers the object constant or not, which depends on how it was created. What the underlying machine / OS assures may be related, but is not that relevant there. Commented Mar 18, 2019 at 21:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.