2

I am looking for a way to extract the types of an std::tuple to define a method signature. Take the following (contrived) example:

template <typename RetT, typename... ArgsT>
class A
{
public:
    typedef RetT ReturnType;
    typedef std::tuple<ArgsT...> ArgTypes;

    RetT doSomething(ArgsT... args)
    {
        // Doesn't make much sense, but it's just an example
        return (RetT) printf(args...);
    }
};

template <typename Enable, typename RetT, typename... ArgsT>
class AAdapter;

// Simply pass arguments along as-is
template <typename RetT, typename... ArgsT>
class AAdapter<std::enable_if_t<!std::is_same_v<RetT, float>>, RetT, ArgsT...> : public A<RetT, ArgsT...> {};

// Add additional first argument if RetT is float
template <typename RetT, typename... ArgsT>
class AAdapter<std::enable_if_t<std::is_same_v<RetT, float>>, RetT, ArgsT...> : public A<RetT, const char*, ArgsT...> {};



template <typename RetT, typename... ArgsT>
class B
{
public:
    typedef AAdapter<void, RetT, ArgsT...> AAdapter;

    // This needs to have the same method signature (return type and argument types) as AAdapter::doSomething()
    template <size_t... Index>
    typename AAdapter::ReturnType doSomething (
        typename std::tuple_element<Index, typename AAdapter::ArgTypes>::type... args
    ) {
        return a.doSomething(args...);
    }

public:
    AAdapter a;
};


int main(int argc, char** argv)
{
    // I would like to be able to remove the <0,1,2> and <0,1,2,3> below.
    B<int, const char*, int, int> b1;
    b1.doSomething<0,1,2>("Two values: %d, %d\n", 1, 2);

    B<float, const char*, int, int> b2;
    b2.doSomething<0,1,2,3>("Three values: %s, %d, %d\n", "a string", 1, 2);

    return 0;
}

Consider the way in which AAdapter changes, adds or removes argument types opaque. Basically, I want B::doSomething() to simply redirect to B::AAdapter::doSomething(), so I want both of these methods to have the exact same signature. The question is: How do I get the argument types of B::AAdapter::doSomething() from inside B?

My definition of B::doSomething() in the code above is the furthest I have come: I'm typedef'ing an std::tuple with the argument types inside A, so I can unpack them back to a parameter pack in B. Unfortunately, with the approach above I still need to provide the Index... template parameters manually when calling B::doSomething(). Surely there must be a way to have these Index... parameters automatically deduced from the size of the tuple. I have thought about approaches using std::make_integer_sequence, but that would require me to define an additional method argument for the sequence itself (and it can't be the last argument with a default value because no other arguments are allowed after a parameter pack).

Is there any way I can do this, with or without std::tuple? Solutions that require C++17 will be fine.

EDIT 1:

I realize now that I could probably circumvent the problem in my particular application by having B inherit from AAdapter instead of having an AAdapter object as a member, but I would still like to know how to solve the problem without having to do that.

EDIT 2:

Maybe some additional info on why AAdapter exists and what I want to achieve. I am implementing a kind of wrapper class around an existing C API that actually needs to be called in another process, RPC-style. So if the user wants to call a C function in the remote process, they will instead call a corresponding method in my wrapper class locally that handles all the RPC stuff like type conversions, the actual remote call and other ugly details. This wrapper class is represented by B in my code above. Now my wrapper method signature will usually not have the exact same signature as the C function. For example, the wrapper may have std::string_view instead of a pair of const char*, size_t that the C function has. For reasons that are not important here, it also needs to have an output parameter (a pointer) where the C function has a return value instead sometimes.

In order for me to not have to define two separate method signatures (in actuality it is three) and write code to convert the parameters for every single one, I instead pass only one of the signatures as template parameters RetT, ArgsT... to B. A signature conversion class (AAdapter in the example above) then applies rules for how to generate the second signature automatically from this first one by adding parameters, changing their types, etc.. A would then hold this generated signature, and B would have the one I provided initially. However, I want B to provide an invoke() method with the signature of A, thus hiding A and the entire method signature mess from the user completely. This is why I need access to the template parameter types of A from within B, and why I can't simply remove the middle class AAdapter.

Alemarius Nexus
  • 340
  • 3
  • 12
  • You can use `std::tuple_element_t` to get the index th type in class tuple_type. – Anonymous1847 Aug 12 '20 at 01:31
  • @Anonymous1847 That's what I tried to do in the example, but I need to access all of the elements, not just one with a specific index, and generating this list of indices without the caller having to specify them manually is essentially what I'm asking about. – Alemarius Nexus Aug 12 '20 at 02:48

2 Answers2

2

The core of your problem is turning a tuple into an argument pack.

maybe the tuple type is not the template arguments? in this case, there is a simple solution by inheritance:

#include <vector>
#include <iostream>
#include <tuple>

template<typename... Types>
struct BImpl{
    typedef std::tuple<std::vector<Types>...> tuple_type;
    // maybe you will get a tuple type from some class templates. assume the 'tuple_type' is the result.
    // requirement: 'tuple_type' = std::tuple<SomeTypes...>
    // requirement: 'tuple_type' can be deduced definitely from template arguments 'Types...'.
    template<typename> // you can add other template arguments, even another std::tuple.
    struct OptCallHelper;
    template<typename... Args>
    struct OptCallHelper<std::tuple<Args...>>{
        auto dosomething(Args&&... args) /* const? noexcept? */{
            // do what you want...
            // requirement: you can definitely define the 'dosomething' here without any other informations.
            std::cout << "implement it here." << std::endl;
        }
    };
    typedef OptCallHelper<tuple_type> OptCall;
};

template<typename... Types>
struct B : private BImpl<Types...>::OptCall{
    typedef typename BImpl<Types...>::OptCall base;
    using base::dosomething;
    // obviously, you can't change the implementation here.
    // in other words, the definition of 'dosomething' can only depend on template arguments 'Types...'.
};


int main(){
    B<int, float> b;
    b({}, {}); // shows "implement it here."
    return 0;
}

you can do what you want to do in BImpl and then use B instead.

   // This needs to have the same method signature (return type and argument types) as AAdapter::doSomething()
   template <size_t... Index>
   typename AAdapter::ReturnType doSomething (
       typename std::tuple_element<Index, typename AAdapter::ArgTypes>::type... args
   ) {
       return a.doSomething(args...);
   }

for AAdaptor, I think you just want the interface of dosomething in A, and you can deduce it:

#include <iostream>

template<typename...>
struct AAdaptor{
    int dosomething(){
        std::cout << "???" << std::endl;
        return 0;
    }
};
// ignore the implementation of AAdaptor and A.
// just consider of how to get the interface of 'dosomething'.

template<typename... Types>
struct BImpl{
    typedef AAdaptor<Types...> value_type;
    typedef decltype(&value_type::dosomething) function_type;
    // attention: it won't work if 'AAdaptor::dosomething' is function template or overloaded.
    //            in this case, you should let A or AAdaptor give a lot of tuples to declare 'dosomething', referring to the first solution.

    

    template<typename>
    struct OptCallHelper;
    template<typename Ret, typename Klass, typename... Args>
    struct OptCallHelper<Ret(Klass::*)(Args...)>{
        value_type data;
        Ret dosomething(Args... args){
            return data.dosomething(args...);
        }
    };
    // attention: 'Ret(Klass::*)(Args...)' is different from 'Ret(Klass::*)(Args...) const', 'noexcept' as well in C++17.
    //            even Ret(Klass::*)(Args..., ...) is also different from them.
    //            you have to specialize all of them.

    typedef OptCallHelper<function_type> OptCall;
};

template<typename... Types>
struct B : BImpl<Types...>::OptCall{
    typedef typename BImpl<Types...>::OptCall base;
    using base::dosomething;
};


int main(){
    B<int, float> b;
    b(); // shows "???"
    return 0;
}

if there is some difference between this code and your requirement, try to give another example to imply some of your implementation. it's still not clear what B gets and should do.

RedFog
  • 1,005
  • 4
  • 10
  • The `OptCallHelper` stuff seems like a good solution. It is kinda similar to what I've since settled on, which is: don't try to have `doSomething()` deduced in `B`, but simply have `B` inherit from `A` (via `AAdapter`), thus inheriting `A::doSomething()` directly. This answer may be the better solution if outright inheritance from `A` is "too much". The purpose of `AAdapter` is still a bit different: It would be responsible for determining the types that make up `BImpl::tuple_type` in your first snippet, but your solution could be adapted by simply having it in between `B` and `BImpl` I think. – Alemarius Nexus Aug 16 '20 at 04:56
-1

This demonstrates how you can get a function with the argument types from a tuple:

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

template <
        typename ArgTuple
>
class B_Class {};

template <typename... ArgTypes>
class B_Class<std::tuple<ArgTypes...> > {
public:
        static void b(
                ArgTypes...
        ) {
                std::cout << "successful call" << std::endl;
        }
};

int main() {
        using ArgTypes = std::tuple<int, char, float, double>;
        int i; char c; float f; double d;
        B_Class<ArgTypes>::b(i, c, f, d);
}

This compiles and prints "successful call" when run.

Anonymous1847
  • 2,568
  • 10
  • 16
  • This does not compile on VS2019 (" 'Ints': is not a member of 'std::integer_sequence' "). I don't think `std::integer_sequence::Ints` exists, which is kinda the problem. `Ints` is a template parameter (pack) of `std::integer_sequence`, but it shouldn't be accessible as a type from outside, because you can't typedef a parameter pack. After all, if this were possible, I could do the exact same for `ArgsT...` in `A` and just use `typename AAdapter::ArgsT...` as argument types. Although this would be ambiguous between the `ArgsT...` of `AAdapter` and of `A`. – Alemarius Nexus Aug 12 '20 at 08:53
  • @Alemarius Nexus My bad, I was looking at https://en.cppreference.com/w/cpp/utility/integer_sequence and I saw the "Ints" there and perhaps I thought it was under members. In any case see if std::integer_sequence could help you. – Anonymous1847 Aug 12 '20 at 22:49
  • @Alemarius Nexus I edited my answer with something that works for me. – Anonymous1847 Aug 12 '20 at 23:19
  • I think you're not understanding my question properly. Your implementation completely removes the middle class AAdapter, which is crucial to what I'm trying to do in my real code (this is just a condensed example of course). The second template specialization of AAdapter **adds** an additional argument to the `ArgsT...` of `A` that `B` doesn't know about, which is missing in your answer. It is also important that this argument gets added by `AAdapter` and not simply by `B` itself. I will edit my question to give a bit more insight into why I want to do that. – Alemarius Nexus Aug 13 '20 at 04:48
  • The core of your problem is turning a tuple into an argument pack. You could add more template arguments into the B_Class specialization and have it specialize for AAdapter instead of std::tuple. – Anonymous1847 Aug 13 '20 at 19:38