Reputation: 1383
I'm trying to create a function that basically takes in a function pointer as well as all of its arguments, and store the function and these arguments in a std::function and std::tuple for later use. I of course saw the typical pattern of a function that consumes variadic arguments, perfectly forwarding them to some place for storage. I saw this solution in How to store variadic template arguments?, which worked well for my situation, until I realized that it seems to only accept function parameters that are either wrapped in std::ref()
or that are std::move()
d, and I simply can't store variable values.
While searching around more for another solution that allowed me to store parameters by value and reference, I came across c++ store function and parameter list for later use, which I intend to repurpose. But at the same time, I really want to understand why the first solution doesn't let me pass anything by value:
template <typename... Ts>
class TupleCarrier {
public:
TupleCarrier(Ts&&... args): args_(std::make_tuple(std::forward<Ts>(args)...)) {}
std::tuple<Ts...> args_;
};
template <typename... Ts>
void CreateCarrier(Ts&&... args) {
TupleCarrier<Ts...> carrier(std::forward<Ts>(args)...);
}
int main() {
int test = 4;
CreateCarrier(std::ref(test), /*std::ref(*/test/*)*/); // fails to compile
return 0;
}
Is it as simple as the fact that CreateCarrier
is taking the arguments as rvalue references, and std::forward is either forwarding them as rvalues or lvalue references, but there is simply no way to pass a regular old lvalue here? I would expect that the lvalue would at least be implicitly treated as an lvalue reference, which could of course be problematic if you were intended to pass by value, but at least it would compile...
Anyways, I don't think I understand the subtleties of the above enough to come up with a correct answer myself, so some sort of explanation here would really help me understand some slightly more advanced C++ :)
Upvotes: 0
Views: 899
Reputation: 634
The issue is in the manner in which std::make_tuple() is defined: It std::decay<>s its argument types.
Examine https://en.cppreference.com/w/cpp/utility/tuple/make_tuple the "Possible Implementation" section to see the details.
So, what happens is, the _args member ends up being:
std::tuple<std::reference_wrapper<int>,int &>
since no decay is added in there. The result of make_tuple() however is std::tuple< int&, int > - the identity of your int is lost in the process - you would have a reference to a value argument to the initializer - which would be a hanging reference. Hence the int& in your 1th tuple position cannot be initialized by pass-by-value int argument.
Anyway, the solution is to also decay the args_ declaration in this manner:
std::tuple< std::decay_t< Ts > ...> args_;
and then things compile fine because you have matched the receiver (args_) with the result of make_tuple() which internally decays its arguments.
Then the type of args_ then ends up being:
std::tuple<std::reference_wrapper<int>,int>
and all is copasetic.
Upvotes: 2