问题描述
我正在寻找一种提取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
将具有我最初提供的签名。但是,我希望A
为B
提供带有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);
}
这会在运行时编译并打印“成功呼叫”。