Reputation: 1577
I have a function that's looking for an element inside a custom list implementation. To make it const-correct, I can do this neat overload and even use a one-liner to reuse the very same implementation, thus not having to duplicate logic.
const MyObject* findObject(const MyList& list)
{
//...
}
MyObject* findObject(MyList& list)
{
return const_cast<MyObject*>(findObject(const_cast<const MyList&>(list)));
}
The problem is, what do I do once I need to return multiple element pointers inside a vector barring unsave/non-portable hacks like reinterpret_cast?
std::vector<const MyObject*> findObject(const MyList& list)
{
//...
}
std::vector<MyObject*> findObject(MyList& list)
{
// this is sth I'm looking for:
const_element_cast<std::vector<MyObject*>>( findObject( const_cast<const MyList&>(list)) );
}
Upvotes: 1
Views: 1323
Reputation: 3937
Option 1
If you are willing to pay the loop + copy price (vs. copy-pasting code) :
std::vector<const thing*> c_vec = static_cast<const me*>(this)->my_const_func();
std::vector<thing*> ret;
for (const thing* t : c_vec) {
// Yes you can do this, since this func isn't const
// so you are guaranteed to be inside non-const context.
// See Scott Meyers.
ret.push_back(const_cast<thing*>(t));
}
return ret;
Option 2
[edit] Here is another alternative if you do not want to pay the loop + copy price.
Assuming you are working with a cpp file (not header-only). Inside your compile unit (.cpp), add a templated getter.
template <class T, class MyObj>
std::vector<T*> getter_imp(MyObj& obj, int someval) {
assert(someval <= 42);
std::vector<T*> ret;
for (auto& i : obj._my_vec) {
if (i < someval) {
ret.push_back(&i);
}
}
return ret;
}
To keep things clean and "hidden", you can friend that function in your class.
template <class T, class MyObj>
friend std::vector<T*> getter_imp(MyObj&, int);
Now, you can call the getter from both const and non-const member functions. Without copy-paste (aka the bane of our lives).
std::vector<const int*> get(int v) const {
return getter_imp<const int>(*this, v);
}
std::vector<int*> get(int v) {
return getter_imp<int>(*this, v);
}
Whats happening here?
You are encoding const
inside templated arguments. Lets say you call from the const
function, T = const int
and MyObj = const your_class
. Inside the function, const
gets deduced into auto&
as well.
When calling from a non-const
context, the template arguments and auto&
aren't const
. So it works.
Upvotes: 0
Reputation: 27528
The easiest solution is to forget about const_cast
and just implement both overloads explicitly. This is what standard-library implementations do.
For example, std::vector
has a const
and non-const
version of operator[]
. Both VC++ and GCC have duplicate implementations for that operator (see include/vector
or stl_vector.h
files, respectively); neither resorts to any const_cast
tricks to duplicate the behaviour.
Now, if your findObject
implementation is very big and complicated, then the first choice should be to make it simpler. As a temporary workaround, you may consider implementing both overloads in terms of an internal (private or otherwise inaccessible) template function, using a decltype
trailing return type to get the correct const
or non-const
return type via the argument. Here is a simple example:
#include <iostream>
#include <vector>
#include <typeinfo> // just to demonstrate what's going on
// simple example data structure:
struct MyObject {};
struct MyList
{
MyObject elements[3] = { MyObject {}, MyObject {}, MyObject {} };
};
namespace internal
{
// &list.elements[0] will be either MyObject const* or MyObject*
template <class ConstOrNonConstList>
auto doFindObject(ConstOrNonConstList& list) -> std::vector<decltype(&list.elements[0])>
{
// let's say there was an immensely complicated function here,
// with a lot of error potential and maintenance nightmares
// lurking behind a simple copy & paste solution...
// just to demonstrate what's going:
std::cout << typeid(decltype(&list.elements[0])).name() << "\n";
std::vector<decltype(&list.elements[0])> result;
for (auto&& element : list.elements)
{
result.push_back(&element);
}
return result;
}
}
std::vector<const MyObject*> findObject(const MyList& list)
{
std::cout << "const version\n";
return internal::doFindObject(list);
}
std::vector<MyObject*> findObject(MyList& list)
{
std::cout << "non-const version\n";
return internal::doFindObject(list);
}
int main()
{
MyList list1;
MyList const list2;
auto obj1 = findObject(list1);
auto obj2 = findObject(list2);
}
Example output (depending on what kind of names typeid
produces on your implementation):
non-const version
struct MyObject *
const version
struct MyObject const *
But frankly, I would not do this. It seems over-engineered, and it's perhaps a bit too clever. Overly clever code is rarely a good idea, because it confuses people.
Upvotes: 2