In the std namespace, there is a convenient std::apply function that allows you to perform an action on each element of the tuple. Using structured binding, we can achieve the same behavior for non-tuple types. Suppose we consider a type with only one non-static data member. Therefore, I wrote the following code:
#include <iostream>
#include <functional>
#include <utility>
#include <type_traits>
template<class F, class T>
decltype(auto) apply1(F&& func, T&& val){
if constexpr(std::is_lvalue_reference<decltype(val)>{}){
auto& [m1] = val;
return std::invoke(std::forward<F>(func), m1);
} else {
auto&& [m1] = std::forward<T>(val);
return std::invoke(std::forward<F>(func), std::forward<decltype(m1)>(m1));
}
}
//tests
struct obj{int v;};
struct data{obj o;};
void ref(obj&){std::cout<<"&\n";}
void cref(const obj&){std::cout<<"const&\n";}
void temp(obj&&){std::cout<<"&&\n";}
int main(){
data d{};
apply1(ref, d);
apply1(cref, d);
//SHOULD NOT COMPILE apply1(temp, d);
apply1(temp, std::move(d));
}
As you can see, the apply1 function has branching depending on the type of reference and therefore looks strange. But without this, the above tests will not work
Q: Is it possible to write the apply1 in a more elegant way without using branching? but at the same time, the above tests should work
I don't see a way to avoid if constexpr before C++23's std::forward_like becomes available, but you could slightly simplify the implementation:
template<class F, class T>
decltype(auto) apply1(F&& func, T&& val) {
auto& [m1] = val;
if constexpr (std::is_lvalue_reference_v<T&&>)
return std::invoke(std::forward<F>(func), m1);
else
return std::invoke(std::forward<F>(func), std::move(m1));
}
You don't need std::forward in the else branch because you already know the value category of (perfectly forwarded) val.
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