Search code examples
c++c++14template-meta-programming

Transform each of parameter pack's values based on a boolean criteria


I am trying to solve this problem in C++ TMP where in i need to convert one parameter pack types into another, and then convert back the types and also values. The conversion back part is based on a boolean criteria that whether an arg in Args... was transformed or not in the first place.

Basically, i have a pack(Args...). First, i transform this (for each args[i], call a transform function). It works like this: For each arg in Args..., just create same type in transformed_args... unless it is one of following, in that case do following conversions:

Type In Args... Type In transformed_Args...
SomeClass shared_ptr to SomeClass
std::vector of SomeClass std::vector of shared_ptr to SomeClass

everything else remains the same for ex: int remains int std::string remains std::string

I achieve this by template specialization, of course For the next part, i take transformed_args..., publish a class and a functor. I receive call back on this functor from(C++generated Python using Pybind, not important though). Relevant bits of that class look like this...

template<typename C, typename...transformed_args..., typename... Args>
class SomeTemplateClass
{
    MethodWrapper<C,void, Args...> func;
    //.....
    void operator()(transformed_args... targs)
    {
        //....
        (*func.wrapped_method_inside)(transform_back_magic(targs)...)   // this is want i want to achieve.
        //transform_back_magic(targs)... is a plaeholder for code that checks if type of args[i]... != type of targs[i]... and then calls a tranform_back specialization on it else just return args[i].val
    }
}

targs are in transformed_args... format, but underlying C++ function they are aimed for expects Args...

template<typename... Args, typename... transformed_args, ........whatever else is needed>
transform_back_magic(....)
{
    if(Args[i].type != transformed_args[i].types)
        tranform_back(targs[i]...);
}

the tranform_back function template logic is specialized for different cases and all logic is in place. But how to invoke that based on this boolean criteria is hitting my TMP knowledge limits. I just got started not many weeks ago.


Here i am listing down what i have created so far. First of all this is what i need in pseudo code

template<typename C, typename... transformed_args, typename... Args>
class SomeTemplateClass
{
    MethodWrapper<C,void, Args...> func;
    void operator(transformed_args... targs)
    {
        **//In pseudo code, this is what i need**
        Args... params = CreateArgsInstanceFromTransformedArgs(targs);
        (*func.wrapped_method_inside)(params...);
    }
}

In my attempt to implement this, so far I have decided on creating a tuple<Args...> object by copying data from targs(with conversions where ever required)

void operator(transformed_args... targs)
{
    //....
    

auto mytup = call1(std::tuple<args...>(), std::make_index_sequence<sizeof...(Args)>, 
std::make_tuple(targs...), targs...);
    

// mytup can be std::tuple<Args...>(transform_back(1st_targs), transform_back(2nd_targs)....). Once available i can write some more logic to extract Args... from this tuple and pass to(*func.wrapped_method_inside)(....)
    (*func.wrapped_method_inside)(ArgsExtractorFromTuple(mytup)); // this part is not implemented yet, but i think it should be possible. This is not my primary concern at the moment
}

//call1


 template<typename... Args, typename... Targs, std::size_t... N>
    auto call1(std::tuple<Args...> tupA, std::index_sequence<N>..., std::tuple<Targs...> tupT, Targs ..)
{
    auto booltup = tuple_creator<0>(tupA, tupT, nullptr);   // to create a tuple of bools
    auto ret1 = std::make_tuple<Args...>(call2(booltup, targs, N)...); // targs and N are expanded together so that i get indirect access to see the corresponding type in Args...
    return ret1;
}
// tuple_creator is a recursive function template with sole purpose to create a boolean tuple.
// such that std::get<0>(booltup) = true, 
//if tuple_element_t<0,std::tuple<Args...>> and tuple_element_t<0,std::tuple<targs...>> are same types else false

template<size_t I, typename... Targs, typename... Args>
auto tuple_creator(std::tuple<Args...>tupA, std::tuple<Targs...>tupT, std::enable_if_t<I == sizeof...(targs)>*)
{
    return std::make_tuple(std::is_same<std::tuple_element_t<I-1, std::tuple<Targs...>>, std::tuple_element_t<I-1, std::tuple<Args...>>>::value);
}

template<size_t I = 0, typename... Targs, typename... Args>
auto tuple_creator(std::tuple<Args...>tupA, std::tuple<Targs...>tupT, std::enable_if_t<I < sizeof...(targs)>*)
{
    auto ret1 = tuple_creator<I+1>(tupA, tupT, nullptr);
    if(!I)
        return ret1;
    auto ret2 = std::is_same<std::tuple_element_t<I-1, std::tuple<Targs...>>, std::tuple_element_t<I-1, std::tuple<Args...>>>::value;
    return std::tuple_cat(ret1, std::make_tuple(ret2));
}

template<typename TT, typename Tuple>
auto call2(Tuple boolyup, TT t, std::size_t I)
{
    auto ret = transform_back<std::get<I>(booltup)>(t); // error: I is not a compile time constant
    return ret;
}

transform_back is a template that uses a bool template param and enable_if based specialization to decide whether transform an argument back or not

below are the transform_back specialization for std::vector. Similarly i have others for when T = Class etc and so on

 template<bool sameTypes, typename T>
    std::enable_if_t<(is_vector<T>::value, is_shared_ptr<typename T::value_type>::value && 
is_class<remove_cvref_t<typename T::value_type_element_type>>::value 
&& sameTypes), T>
    transform_back(T val)       // it was never transfoemd in first place, return as is
    {
        return val;
    }
    
    template<bool sameTypes, typename T>
    std::enable_if_t<(is_vector<T>::value, is_shared_ptr<typename T::value_type>::value 
&& is_class<remove_cvref_t<typename T::value_type_element_type>>::value 
&& !sameTypes), 
typename std::vector<typename T::value_type::element_type>>
    transform(T val)
    {
        std::vector<T::value_type::element_type> t;
        for(int i = 0 ; i < val.size(); ++i)
        {
            typename T::value_type::element_type obj = *val[i];
            t.push_back(obj);
        }
        return t;
    }

Both these specialization are same and only differ on sameTypes boolean variable

This code currently errors out in call2 method while trying to using

std::get
auto ret = transform_back<std::get<I>(booltup)>(t); // error: I is not a compile time constant

How can you help?

1)What could be the work around to std::get issue here? Just cant figure out a way to fit in std::size_t as template arg here instead of function arg to make it work at compile time. Other than this:

2)If you can suggest an alternative approach to implement from top level.

Args... params = CreateArgsInstanceFromTransformedArgs(targs);

That would be great. The path i took is not very convincing personally to me.


Solution

  • If I understand correctly, you might do something like:

    template <typename> struct Tag{};
    
    std::shared_ptr<SomeClass> transform_to(Tag<std::shared_ptr<SomeClass>>, const SomeClass& s)
    {
        return std::make_shared<SomeClass>(s);
    }
    
    std::vector<std::shared_ptr<SomeClass>> transform_to(Tag<std::vector<std::shared_ptr<SomeClass>>>, const std::vector<SomeClass>& v)
    {
        std::vector<std::shared_ptr<SomeClass>> res;
        res.reserve(v.size());
        for (const auto& s : v) {
            res.emplace_back(std::make_shared<SomeClass>(s));
        }
        return res;
    }
    
    const SomeClass& transform_to(Tag<SomeClass>, const std::shared_ptr<SomeClass>& s)
    {
        return *s;
    }
    
    std::vector<SomeClass> transform_to(Tag<std::vector<SomeClass>>, const std::vector<std::shared_ptr<SomeClass>>& v)
    {
        std::vector<SomeClass> res;
        res.reserve(v.size());
        for (const auto& s : v) {
            res.emplace_back(*s);
        }
        return res;
    }
    
    template <typename T>
    const T& transform_to(Tag<T>, const T& t) { return t; } // No transformations
    

    And then

    std::function<void (Args...)> func;
    
    template <typename ... transformed_args>
    void operator () (transformed_args... targs) const
    {
        func(transform_to(Tag<Args>(), targs)...);
    }