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

Started by
6 comments, last by irreversible 6 years, 6 months ago

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? :)

Advertisement

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.

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>());
        }
};

 

 

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.

 

 

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();

 

1 hour ago, irreversible said:

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

Could you elaborate on that? (just interested)

1 minute ago, ninnghazad said:

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.

This topic is closed to new replies.

Advertisement