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
.