Reputation:
Okay, I did some research and apparently there are a lot of duplicate questions on SO on this topic, to name a few:
etc. But I cannot help but raise this again, because
auto
-typed return value, I am literally duplicating the function body with the only difference being the const
function qualifier.const
version and non-const
version may return types that are totally incompatible from each other. In such cases, neither Scott Meyers's const_cast
idiom, nor the "private const
function returning non-const
" technique would work.As an example:
struct A {
std::vector<int> examples;
auto get() { return examples.begin(); }
auto get() const { return examples.begin(); }
}; // Do I really have to duplicate?
// My real-world code is much longer
// and there are a lot of such get()'s
In this particular case, auto get()
returns an iterator
while auto get() const
returns a const_iterator
, and the two cannot be converted into each other (I know erase(it,it)
trick does the conversion in this case but that's not the point). The const_cast
hence does not work; even if it works, that requires the programmer to manually deduce auto
type, totally defeating the purpose of using auto
.
So is there really no way except with macros?
Upvotes: 7
Views: 2827
Reputation: 416
struct A {
std::vector<int> examples;
private:
template <typename ThisType>
static auto
get_tmpl(ThisType& t) { return t.examples.begin(); }
public:
auto get() { return get_tmpl(*this); }
auto get() const { return get_tmpl(*this); }
};
How about the above? Yes, you still need to declare both methods, but the logic can be contained in a single static template method. By adding a template parameter for return type this will work even for pre-C++11 code.
Upvotes: 2
Reputation: 18081
Just a hypothetical solution, that I am thinking about applying every where once we will have concepts: using friend free function in place of member function.
//Forwarding ref concept
template<class T,class U>
concept FRef = std::is_same_v<std::decay_t<T>,std::decay_t<U>>;
struct A{
std::vector<int> examples;
friend decltype(auto) get(FRef{T,A}&& aA){
return std::forward<T>(aA).examples.begin();
//the std::forward is actualy not necessary here
//because there are no overload value reference overload of begin.
}
};
Upvotes: 0
Reputation: 2067
Ok, so after a bit of tinkering I came up with the following two solutions that allow you to keep the auto return type and only implement the getter once. It uses the opposite cast of what Meyer's does.
This version simply returns both versions in the implemented function, either with cbegin()
or if you don't have that for your type this should work as a replacement for cbegin()
: return static_cast<const A&>(*this).examples.begin();
Basically cast to constant and use the normal begin()
function to obtain the constant one.
// Return both, and grab the required one
struct A
{
private:
// This function does the actual getter work, hiding the template details
// from the public interface, and allowing the use of auto as a return type
auto get_do_work()
{
// Your getter logic etc.
// ...
// ...
// Return both versions, but have the other functions extract the required one
return std::make_pair(examples.begin(), examples.cbegin());
}
public:
std::vector<int> examples{ 0, 1, 2, 3, 4, 5 };
// You'll get a regular iterator from the .first
auto get()
{
return get_do_work().first;
}
// This will get a const iterator
auto get() const
{
// Force using the non-const to get a const version here
// Basically the inverse of Meyer's casting. Then just get
// the const version of the type and return it
return const_cast<A&>(*this).get_do_work().second;
}
};
This one should be better since it only returns one value and it is known at compile time which value is obtained, so auto
will know what to do. Otherwise the get()
functions work mostly the same.
// With if constexpr
struct A
{
private:
// This function does the actual getter work, hiding the template details
// from the public interface, and allowing the use of auto as a return type
template<bool asConst>
auto get_do_work()
{
// Your getter logic etc.
// ...
// ...
if constexpr (asConst)
{
return examples.cbegin();
// Alternatively
// return static_cast<const A&>(*this).examples.begin();
}
else
{
return examples.begin();
}
}
public:
std::vector<int> examples{ 0, 1, 2, 3, 4, 5 };
// Nothing special here, you'll get a regular iterator
auto get()
{
return get_do_work<false>();
}
// This will get a const iterator
auto get() const
{
// Force using the non-const to get a const version here
// Basically the inverse of Meyer's casting, except you get a
// const_iterator as a result, so no logical difference to users
return const_cast<A&>(*this).get_do_work<true>();
}
};
This may or may not work for your custom types, but it worked for me, and it solves the need for code duplication, although it uses a helper function. But in turn the actual getters become one-liners, so that should be reasonable.
The following main function was used to test both solutions, and worked as expected:
int main()
{
const A a;
*a.get() += 1; // This is an error since it returns const_iterator
A b;
*b.get() += 1; // This works fine
std::cout << *a.get() << "\n";
std::cout << *b.get() << "\n";
return 0;
}
Upvotes: 2