从std :: tuple中提取类型以获取方法签名

问题描述

我正在寻找一种提取std::tuple类型来定义方法签名的方法。以以下(人为)示例为例:

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,const char*,ArgsT...> {};



template <typename RetT,typename... ArgsT>
class B
{
public:
    typedef AAdapter<void,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,2,3> below.
    B<int,int,int> b1;
    b1.doSomething<0,2>("Two values: %d,%d\n",2);

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

    return 0;
}

考虑AAdapter更改,添加或删除不透明的参数类型的方式。基本上,我希望B::doSomething()仅重定向到B::AAdapter::doSomething(),所以我希望这两种方法都具有完全相同的签名。问题是:如何从B::AAdapter::doSomething()内部获得B的参数类型?

我在上面的代码中对B::doSomething()的定义是我得到的最详尽的信息:我正在std::tuple中使用参数类型对A进行类型定义,因此可以解压缩它们返回到B中的参数包。不幸的是,使用上述方法时,我仍然需要在调用Index...时手动提供B::doSomething()模板参数。当然,必须有一种方法可以从元组的大小中自动推导出这些Index...参数。我已经考虑过使用std::make_integer_sequence的方法,但这将要求我为序列本身定义一个附加的方法参数(并且它不能是具有默认值的最后一个参数,因为在参数之后不允许其他参数包)。

无论有没有std :: tuple,有什么办法可以做到这一点?需要C ++ 17的解决方案就可以了。

编辑1:

我现在意识到,可以通过B的{​​{1}} 继承而不是将AAdapter对象作为对象来解决特定应用程序中的问题。成员,但我仍然想知道如何解决问题而不必这样做。

编辑2:

也许有关AAdapter存在的原因以及我想要实现的目标的其他信息。我正在围绕现有的C API实现一种包装类,而该类实际上需要在另一个过程(RPC样式)中调用。因此,如果用户想在远程进程中调用C函数,他们将在本地调用我的包装器类中的对应方法,该方法处理所有RPC内容,例如类型转换,实际的远程调用和其他丑陋的细节。在我上面的代码中,该包装器类由AAdapter表示。现在,我的包装方法签名通常与C函数没有完全相同的签名。例如,包装器可能具有B而不是C函数具有的一对std::string_view。出于一些不重要的原因,还需要具有输出参数(指针),而C函数有时具有返回值。

为了让我不必定义两个单独的方法签名(实际上是三个),并编写代码以转换每个参数的参数,我只将其中一个签名作为模板参数{{1} }至const char*,size_t。然后,签名转换类(以上示例中的RetT,ArgsT...)将应用规则,以通过添加参数,更改其类型等从该第一个签名自动生成第二个签名。然后B将保留此签名生成的签名,而AAdapter将具有我最初提供的签名。但是,我希望AB提供带有B签名的方法,从而向用户完全隐藏invoke()和整个方法签名。这就是为什么我需要从A内部访问A的模板参数类型的原因,也是为什么我不能简单地删除中间类A的原因。

解决方法

问题的核心是将元组变成参数包。

也许元组类型不是模板参数?在这种情况下,有一个通过继承的简单解决方案:

#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;
}

您可以在BImpl中做您想做的事情,然后改用B

   // 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...);
   }

对于AAdaptor,我认为您只需要dosomething中的A的接口,就可以推断出它:

#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;
}

如果此代码与您的要求之间有一些区别,请尝试给出另一个示例来暗示您的某些实现。尚不清楚B得到什么和应该做什么。

,

这演示了如何从元组中获取具有参数类型的函数:

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

这会在运行时编译并打印“成功呼叫”。

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...