Consider a class Bar
in two versions of a library:
/// v1
class Bar
{
void get_drink()
{
std::cout << "non-const get_drink() called" << std::endl;
}
};
/// v2
class Bar
{
void get_drink()
{
std::cout << "non-const get_drink() called" << std::endl;
}
void get_drink() const
{
std::cout << "const get_drink() called" << std::endl;
}
};
On the client side code, we own a Bar
object and would like to get_drink
. I want to be able to prefer calling the const version of get_drink()
if it's available (when using v2 library) and fallback to the non-const version (when using v1 library). That is:
Bar bar;
bar.get_drink(); // this does not call the const version of v2
static_cast<const Bar&>(bar).get_drink(); // this does not compile against v1 library
Unfortunately, the library is not versioned and there's no other way to distinguish between the two versions.
I reckon some template magic has to be used. So the question is how?
There are legitimate uses of having two member functions with the same name with one const and the other not, such as the begin and end iterator functions, which return non-const iterators on non-const objects, and const iterators on const objects, but if it's casting from const to do something, it smells like fish.
Const member functions in C++ It is recommended to use const keyword so that accidental changes to object are avoided. A const member function can be called by any type of object. Non-const functions can be called by non-const objects only.
If the function is non-constant, then the function is allowed to change values of the object on which it is being called. So the compiler doesn't allow to create this chance and prevent you to call a non-constant function on a constant object, as constant object means you cannot change anything of it anymore.
If the thing you are returning by reference is logically part of your this object, independent of whether it is physically embedded within your this object, then a const method needs to return by const reference or by value, but not by non-const reference.
The object called by these functions cannot be modified. It is recommended to use const keyword so that accidental changes to object are avoided. A const member function can be called by any type of object. Non-const functions can be called by non-const objects only.
Obviously you can't call a non- const method from a const method. Otherwise, const would have no meaning when applied to member functions. A const member function can change member variables marked mutable, but you've indicated that this is not possible in your case.
It's better to move as much common functionality as you can into helper functions, then have your const and non-const member functions each do as little work as they need to. In the case of a simple accessor like this, it's just as easy to return m_bar; from both of the functions as it is to call one function from the other.
Any attempt to change the data member of const objects results in a compile-time error. When a function is declared as const, it can be called on any type of object, const object as well as non-const objects.
This seems to work:
#include <type_traits>
#include <iostream>
template<typename T, typename=void>
struct find_a_drink {
void operator()(T &t) const
{
t.get_drink();
}
};
template<typename T>
struct find_a_drink<T,
std::void_t<decltype( std::declval<const T &>()
.get_drink())>
> {
void operator()(T &t) const
{
static_cast<const T &>(t).get_drink();
}
};
template<typename T>
void drink_from(T &&t)
{
find_a_drink<std::remove_reference_t<T>>{}(t);
}
/// v1
class Bar1
{
public:
void get_drink()
{
std::cout << "non-const get_drink() called (Bar1)" << std::endl;
}
};
class Bar2
{
public:
void get_drink()
{
std::cout << "non-const get_drink() called (Bar2)" << std::endl;
}
void get_drink() const
{
std::cout << "const get_drink() called (Bar2)" << std::endl;
}
};
int main()
{
Bar1 b1;
Bar2 b2;
drink_from(b1);
drink_from(b2);
return 0;
}
Result:
non-const get_drink() called (Bar1)
const get_drink() called (Bar2)
This is the same underlying idea as Sam's answer but using expression sfinae, which I think is clearer
// 1
template<typename T>
auto drink_from(T &t) -> decltype(static_cast<const T&>(t).get_drink())
{
static_cast<const T &>(t).get_drink();
}
// 2
template<typename T>
auto drink_from(T &&t)
{
t.get_drink();
}
Overload 1 is preferred if the argument t
can be cast to a const
and have get_drink
called on it, which requires the existence of a const-qualified member. If such a member is not available, overload 2 is called, which calls the non-const-qualified member.
Bar b;
drink_from(b); // calls const qualified member if available
demo
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With