Jump to content
  • Advertisement
Sign in to follow this  
Juliean

Remove types from std::tuple<Args...>

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

If you intended to correct an error in the post then please contact us.

Recommended Posts

So what I want to do sounds pretty simple, but I just can't figure it out: I want to remove all types with a certain characteristic from a tuple. For this example, the condition is actually simple: I want to strip all occurances of "void", so

std

std::tuple<bool, void, int, void, void, float, void>;

becomes:

std::tuple<bool, int, float>;

I ususally don't have a problem with crazy template-code, but this is just above me. I mean, I know how to split the tuple-declaration into elements, but cannot figure out how to just remove one element while leaving the others intact:
 

template<typename Arg>
struct StripTuple
{
    using Type = Arg;
};

template<typename Arg, typename... Args>
struct StripTuple<std::tuple<Arg, Args...>>
{
    using type = std::tuple<Arg, Args...>>; // ignores StripTuple for all other call; call to StripTuple would be infinite recursion
};

template<typename... Args>
struct StripTuple<std::tuple<void, Args...>>
{
    using type = StripTuple<Args... >>;
};

Any simple solutions (remove-condition = void can be used; doesn't have to work with SFINEA if it doesn't need to)?

Alternatively, and to give some context, I'd also be happy not to buld the tuple to have voids to begin with. Though I also can't seem to figure that out for the life of me, and I actually belive it would make the code even more complex than just splitting it into two steps, but let me describe the problem (but beware, if you have a solution that works on the requirements above post this before, as I think the actual problem might be hard to get across for me):

For my script-function binding, I recently refactored the type-system to handle types with a specializable trait class, which ie. can be defined for value-types:

template<typename Type>
struct AttributeSupplier<Type, sys::EnableIf<core::IsTypeSystemType<Type> && !core::IsObject<Type>, void>>
{
  static Type GetAttribute(core::ConstVariableView value)
  {
    return value.GetValue<Type>();
  }

  static core::TypeId GetTypeId(void)
  {
    return core::generateTypeId<Type>();
  }
};

The important thing about it, which leads to my "problem", is that this works recursively - when the trait is used, it will unfold the argument-list of the "GetAttributes"-method and forward the aquisition to the next AttributeSupplier. As with GetTypeId, this can be omitted, in which case it will do the same thing - aquire the type-id by unfolding the argument-list and checking those types' suppliers.

So what the code that requires stripping the tuple of voids does, is try to access the underlying type of any type(s) it receives, by performing the type-unfolding. Thats necessary for multiple reason, for once the type-system can define a runtime-specific value (ie. EntityHandle which is aquired via EntityID), so the underlying type should be "EntityID". Also, to simplify parsing of a function-declaration, this should allow me to pack all argument-type-ids into a single tuple, unfolding all types (which can also include pair/tuple itself). Stripping the void is for system-supplied values - those do not have a type-id, even after unfolding the entire chain of types, so for the UnderlyingType-definition, they need to be removed. So ie., for a function-signature "void f(int, Entity&, DeltaTime, std::pair<float, bool>)", this should result in the tuple-type "std::tuple<int, Entity&, float, bool>". which is done with this code:
 

struct UnderlyingTypeHelper
{
    using InvalidType = void;

    template<typename Arg, typename Enable = void>
    struct Helper
    {
    };

    // helper for recursion
    template<typename Arg>
    using HelperType = typename Helper<Arg>::type;

private:

    // if the arguments trait defines a type-id, we're done
    template<typename Arg>
    struct Helper<Arg, sys::EnableIf<HasAttributeTypeId<Arg>, void>>
    {
        using type = Arg;
    };

    // values not useable as attributes are meant to be removed/set to void
    template<typename Arg>
    struct Helper<Arg, sys::EnableIf<!CanBeAttribute<Arg>, void>>
    {
        using type = InvalidType; // !!! here's the out to "void" => this would need to somehow not make it into the tuple created below
    };

    // forwarded type-id => container-structs, runtime-mapped values
    template<typename Arg>
    struct Helper<Arg, sys::EnableIf<!IsTuple<Arg> && CanBeAttribute<Arg> && !HasAttributeTypeId<Arg>, void>>
    {
        using type = HelperType<AttributeFunctionArgs<Arg>>;
    };

    // tuple => unfold function arguments
    template<typename... Args>
    struct Helper<std::tuple<Args...>>
    {
        using type = std::tuple<HelperType<Args>...>;
    };
};

The last specialization is responsible for building the output-tuple, while the others are for eigther recursively unfolding the arguments (AttributeFunctionArgs stores the GetAttribute-signature as a tuple), and selecting when to stop with a type-id-supplied Arg, or just "void" out if we reach a point where Arg is not a valid attribute anymore.

So yeah, here's some context, and if anyone feels like torturing their brain around some stupid template-code and comes up with a ingenious solution to that, I wouldn't be unhappy. But I doubt that happens, so a solution to the problem stated way above is already more then enough. Thanks ;)

Share this post


Link to post
Share on other sites
Advertisement

Uh, nevermind, found a solution (at least for removing the void from tuple):
 

// concat-tuple => constructs one tuple of various elements, tuples, ...

namespace impl
{
    template<typename... Args>
    struct ConcatTupleHelper
    {
    };

    template<typename... Args, typename... Args2>
    struct ConcatTupleHelper<std::tuple<Args...>, std::tuple<Args2...>>
    {
        using type = std::tuple<Args..., Args2...>;
    };

    template<typename Arg, typename Arg2>
    struct ConcatTupleHelper<Arg, Arg2>
    {
        using type = std::tuple<Arg, Arg2>;
    };

    template<typename Arg, typename... Args>
    struct ConcatTupleHelper<Arg, std::tuple<Args...>>
    {
        using type = std::tuple<Arg, Args...>;
    };

    template<typename... Args, typename Arg>
    struct ConcatTupleHelper<std::tuple<Args...>, Arg>
    {
        using type = std::tuple<Args..., Arg>;
    };
}

template<typename... Types>
using ConcatTupleTypes = typename impl::ConcatTupleHelper<Types...>::type;

// StripTuple -> removes void from the tuple, using ConcatTupleTypes
    
namespace impl
{
    template<typename Arg>
    struct StripTuple
    {
        using type = Arg;
    };

    template<typename Arg>
    using StripTupleType = typename StripTuple<Arg>::type;

    template<>
    struct StripTuple<std::tuple<void>>
    {
        using type = std::tuple<>;
    };

    template<typename Arg, typename Arg2, typename... Args>
    struct StripTuple<std::tuple<Arg, Arg2, Args...>>
    {
        using type = sys::ConcatTupleTypes<Arg, StripTupleType<std::tuple<Arg2, Args...>>>;
    };

    template<typename Arg2, typename... Args>
    struct StripTuple<std::tuple<void, Arg2, Args...>>
    {
        using type = StripTupleType<std::tuple<Arg2, Args...>>;
    };
}    ´

template<typename Arg>
using StripTupleType = impl::StripTupleType;

Damn, thats a lot of boilerplate-code though, all of which is needed from my testings. Well, if someone still wants to wrap their head around the underlying algorithmn & propose some better alternative, I'm all for it :D

Share this post


Link to post
Share on other sites

Hey there, i had fun playing a bit with your issue and i came up with this:

#include <tuple>
#include <string>
#include <utility>
#include <iostream>

template<typename T, typename... Args>
struct CleanTuple
{
};

template<>
struct CleanTuple<std::tuple<void>>
{
	using type = std::tuple<>;
};

template<typename T>
struct CleanTuple<std::tuple<T>>
{
	using type = std::tuple<T>;
};

template<typename T, typename... Args>
struct CleanTuple<std::tuple<T, Args...>>
{
	using type = decltype(
		tuple_cat(
			std::declval<typename CleanTuple<std::tuple<T>>::type>(), 
			std::declval<typename CleanTuple<std::tuple<Args...>>::type>()));
};

void doSomethingWithTuple(std::tuple<int, int, std::string> &a)
{
	std::cout << std::get<0>(a) <<  std::endl;
	std::cout << std::get<1>(a) <<  std::endl;
	std::cout << std::get<2>(a) <<  std::endl;
}

int main()
{
	CleanTuple<std::tuple<int, int, void, std::string, void>>::type a;
	doSomethingWithTuple(a);

	std::tuple<int, std::string, int> b;
	//doSomethingWithTuple(b);
}

Seems to work (if i don't uncomment the commented line, everything compiles). Let me know what you think :P.

Share this post


Link to post
Share on other sites

Ah, thats much nicer than my solution, even after I simplified it a bit :D I'll just have to modify it a bit, as I also need to collapse all tuples recursively - so std::tuple<int, std::tuple<bool, void>, float> becomes std::tuple<int, bool, float>. Thats currently done by this monstrosity:
 

    namespace impl
    {
        template<typename... Args>
        struct MergeTuple
        {
        };

        template<typename... Args, typename... Args2>
        struct MergeTuple<std::tuple<Args...>, std::tuple<Args2...>>
        {
            using type = std::tuple<Args..., Args2...>;
        };

        template<typename... Args>
        using MergeTupleType = typename MergeTuple<Args...>::type;

        template<typename... Args>
        struct ConcatTupleHelper
        {
            using type = std::tuple<Args...>;
        };

        template<typename... Types>
        using ConcatTupleTypes = typename ConcatTupleHelper<Types...>::type;

        template<typename Arg>
        struct ConcatTupleHelper<Arg>
        {
            using type = std::tuple<Arg>;
        };

        template<typename... Args>
        struct ConcatTupleHelper<std::tuple<Args...>>
        {
            using type = ConcatTupleTypes<Args...>;
        };

        template<typename... Args, typename... Args2>
        struct ConcatTupleHelper<std::tuple<Args...>, std::tuple<Args2...>>
        {
            using type = ConcatTupleTypes<Args..., Args2...>;
        };

        template<typename Arg, typename Arg2, typename... Args>
        struct ConcatTupleHelper<Arg, Arg2, Args...>
        {
            using type = MergeTupleType<ConcatTupleTypes<Arg>, ConcatTupleTypes<Arg2, Args...>>;
        };
    }

    template<typename... Types>
    using ConcatTupleTypes = impl::ConcatTupleTypes<Types...>;

But I'm sure it will be easier with your code :)

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!