# Storing one or more lvalue references inside a tuple of a variadic argument list

This topic is 422 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

## Recommended Posts

I'm writing some code for automatic function argument type deduction using a tuple. I'm then iterating over it to narrow down and check each argument separately. I spent a cozy evening tinkering with getting by-value, const value and const references to work, until I discovered that some functions need to return more than one result. This is where I need to identify non-const lvalue references, which a tuple has difficulty in handling.

As far as I can tell most problems out and about on the web focus on creating fairly simple stand-alone tuples that only contain lvalue references using std::tie. In particular, this stackexchange thread outlines how that can be accomplished.

Problem is, I have a packed array of types, which may or may not contain one or more lvalue references interleaved with other types. forward_as_tuple is suggested here and there, but I'm unsure how to use it.

Here's there relevant code:

// split the return type from the argument list
template<typename R, typename... Args>
struct												signature<R(Args...)>
{
using return_type								= R;
using argument_type								= std::tuple<Args...>;				// 1
};

template<typename FUNCSIG>
editor::nodetype_t&									CreateNodeType(
IN const char*									category)
{
// instantiate the argument list tuple. No need to post any further code as this
// is where things fail to compile
signature<FUNCSIG>::argument_type				arglist;							// 2
}

// the below snippet outlines how CreateNodeType() is called:

#define DEFINE_MTL_NODE(function, category, ...)							\
auto& nodeType = CreateNodeType<decltype(function)>(category);

// a sample function for completeness. I'm intentionally not using the return value here.
void Lerp(
IN const math::vec3& ColorIn1,
IN const math::vec3& ColorIn2,
IN float Alpha,
OUT math::vec3& ColorOut) { .. }

void main()
{
DEFINE_MTL_NODE(Lerp, "Color");
}

Either the line marked with 1 or 2 needs to be something else, but apparently my C++ level is not high enough to figure out what. PS - to further complicate things, I'm stuck on C++11 for now.

Ideas?

##### Share on other sites

I've had to do something like this recently, and you'll probably need two types -- one that you can use for traits:

using traits = std::tuple<Args...>;

And one that you can use for instantiating arguments:

using instances = std::tuple<typename std::decay<Args>::type...>;

You can then use the traits to determine which arguments are non-const L-value references, so they can be copied from the instance tuple back into the appropriate reference parameters (depending on how the callable is invoked*).

*std::apply would be ideal, but of course it's not available in C++11. Check out this for an alternative approach.

Edited by Zipster

##### Share on other sites

I have to say this is a bit over my head. I understand what the code is supposed to do conceptually, but even with the link you provided I'm not sure what exactly is happening.

Eg given this (from the link):

template<typename T, typename... Args>
struct foo
{
tuple<Args...> args;

// Allows deducing an index list argument pack
template<size_t... Is>
T gen(index_list<Is...> const&)
{
return T(get<Is>(args)...); // This is the core of the mechanism
}

T gen()
{
return gen(
index_range<0, sizeof...(Args)>() // Builds an index list
);
}
};

How do I even invoke foo? What is T

return T(get<Is>(args)...);

As I understand it, this gets the Is-th (eg last) element from args, then expands the rest and returns it as a separate type.

__________________________________

std::apply makes a bit more sense to me (though still not enough) - I'm not sure how it can be called without instantiating the argument list first, which already generates the compiler error.

My own take on it fails for a different reason. The following is a mix of the signature class from my original post and std::apply with the help of the index classes from Zipster's link. The main problem here is that I'm not sure where or how the lookup between the traits list and the decayed argument list is supposed to happen. I've also replaced std::invoke with a recursive call to mycall() - this effectively bypasses per-element type lookup anyway.

PS - the reinterpret_cast below is a joke. It does work with my basic test case though, which begs the question - ignoring potential issues with portability for the moment, if the tuple element size is  guaranteed to be constant (or even if different core types have different sizes, but qualifiers do not), why would this be illegal?

void mycall() { }

template<typename T, typename ...Args>
void mycall(T&& t, Args&& ...args)
{
lout << "CALLING" << endl;
DOUTEX(std::is_reference<T>::value);
DOUTEX(std::is_const<std::remove_reference<T>::type>::value);

mycall(args...);
}

namespace detail {
template <class Tuple, std::size_t... Is>
void invoke_impl(Tuple&& t, index_list<Is...> const&)
{
mycall(std::get<Is>(std::forward<Tuple>(t))...);
}
}

template<typename S>
struct												sig2;

template <typename R, typename... Args>
struct sig2<R(Args...)>
{
using argument_list				= std::tuple<typename std::decay<Args>::type...>;
using Tuple					= std::tuple<Args...>;

void invoke() {
const auto size				= tuple_size<Tuple>::value;

argument_list				t;

detail::invoke_impl(
// ! type mismatch for cast via std::forward/std::forward_as_tuple:
// forward_as_tuple/*std::forward*/<Tuple>(t),
// but using dumb force actually works with my test case
reinterpret_cast<Tuple&>(t),
index_range<0, size>());
}
};

Edited by irreversible

##### Share on other sites
16 minutes ago, irreversible said:

How do I even invoke foo? What is T


return T(get<Is>(args)...);

As I understand it, this gets the Is-th (eg last) element from args, then expands the rest and returns it as a separate type.

"T" in this case is the constructor of template-type "T" which is declared for foo.

"get<Is>(args)..." gets every element from 0...last. Similar to "std::forward<Args>(args)" for variadic args, this expands to:

T(get<0>(args), get<1>(args), get<2>(args)....);

Think the general term for this is folding.
"Is" is just a sequence of non-type template arguments that go from 0... last-index, based on the index_sequence variable you pass to the function.

##### Share on other sites

After a bit more tinkering, this is what I got. It seems to work and is C++11 compatible. I guess it would be possible to pass in a callback name and have the correct template overload be called, but frankly I don't need that level of control. Besides, this would be so much easier in C++14+.

template<size_t INDEX, typename Tuple, typename T, typename ...Args>
void mycall(T&& t, Args&& ...args)
{
UNREFERENCED_PARAMETERS(t);

// get the real type
using TT							= std::tuple_element<INDEX, Tuple>::type;

// some debug output
lout << "ARGUMENT" << endl;
DOUTEX(std::is_reference<TT>::value);
DOUTEX(std::is_const<std::remove_reference<TT>::type>::value);

// unpack next argument
mycall < INDEX + 1, Tuple > (args...);
}

namespace detail {
// this can be collapsed into ListArgs()
template <class Tuple, class TupleInstances, std::size_t... Is>
void invoke_impl(TupleInstances&& t, index_list<Is...> const&)
{
// start at index 0, passing in the decayed argument list and type traits
mycall<0, Tuple>(std::get<Is>(std::forward<TupleInstances>(t))...);
}

}

template<typename S>
struct									signature;

template <typename R, typename... Args>
struct signature<R(Args...)>
{
using argument_list					= std::tuple<typename std::decay<Args>::type...>;
using Tuple							= std::tuple<Args...>;

void ListArgs() {
const auto size					= tuple_size<Tuple>::value;

detail::invoke_impl<Tuple>(
argument_list(), index_range<0, size>());
}
};

// USAGE:

signature<decltype<SomeFuction>> sig;
sig.ListArgs();

##### Share on other sites
1 hour ago, irreversible said:

Besides, this would be so much easier in C++14+.

Could you elaborate on that? (just interested)

##### Share on other sites

Could you elaborate on that? (just interested)

C++14 allows autos in lambdas. I'm assuming you could collapse your redirection to something like this:

ListArguments([](auto arg) { mycallback(arg); });

As opposed of having to work around your templated callback using some struct hack. As implied above, I can't test this, though.

1. 1
2. 2
Rutin
18
3. 3
4. 4
5. 5

• 26
• 11
• 9
• 9
• 11
• ### Forum Statistics

• Total Topics
633701
• Total Posts
3013443
×