从 std::tuple 中提取类型以获取方法签名
Extract types from std::tuple for a method signature
我正在寻找一种方法来提取 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, 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;
}
考虑 AAdapter 更改、添加或删除不透明参数类型的方式。基本上,我希望 B::doSomething()
简单地重定向到 B::AAdapter::doSomething()
,因此我希望这两种方法具有完全相同的签名。问题是:如何从 B
内部获取 B::AAdapter::doSomething()
的参数类型?
我在上面的代码中对 B::doSomething()
的定义是我最深入的:我正在使用 A
中的参数类型对 std::tuple
进行类型定义,所以我可以在 B
中将它们解压回参数包。不幸的是,使用上述方法我仍然需要在调用 B::doSomething()
时手动提供 Index...
模板参数。当然必须有一种方法可以让这些 Index...
参数自动从元组的大小中推导出来。我考虑过使用 std::make_integer_sequence
的方法,但这需要我为序列本身定义一个额外的方法参数(并且它不能是具有默认值的最后一个参数,因为在参数之后不允许其他参数包)。
有或没有 std::tuple 我有什么办法可以做到这一点吗?需要 C++17 的解决方案就可以了。
编辑 1:
我现在意识到我可以通过 B
inherit from AAdapter
而不是 [=26] 来绕过我的特定应用程序中的问题=]反对作为成员,但我仍然想知道如何解决问题而不必这样做。
编辑 2:
也许还有一些关于为什么 AAdapter
存在以及我想要实现的目标的额外信息。我正在围绕现有的 C API 实现一种包装器 class,它实际上需要在另一个进程中调用,RPC 样式。因此,如果用户想在远程进程中调用 C 函数,他们将在本地调用我的包装器 class 中的相应方法,该方法处理所有 RPC 内容,如类型转换、实际的远程调用和其他丑陋的细节。这个包装器 class 在我上面的代码中由 B
表示。现在我的包装器方法签名通常不会与 C 函数具有完全相同的签名。例如,包装器可能具有 std::string_view
而不是 C 函数具有的一对 const char*, size_t
。由于此处不重要的原因,它还 需要 有一个输出参数(指针),其中 C 函数有时具有 return 值。
为了让我不必定义两个单独的方法签名(实际上是三个)并编写代码来转换每个方法的参数,我只将其中一个签名作为模板参数传递 RetT, ArgsT...
到 B
。签名转换 class(上例中的 AAdapter
)然后应用有关如何通过添加参数、更改其类型等从第一个签名自动生成第二个签名的规则。A
然后将保存这个生成的签名,B
将拥有我最初提供的签名。但是,我希望 B
提供带有 A
签名的 invoke()
方法,从而对用户完全隐藏 A
和整个方法签名混乱。这就是为什么我需要从 B
中访问 A
的模板参数类型,以及为什么我不能简单地删除中间的 class AAdapter
.
这演示了如何从元组中获取具有参数类型的函数:
#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);
}
这会在 运行 时编译并打印“调用成功”。
The core of your problem is turning a tuple into an argument pack.
也许元组类型不是模板参数?在这种情况下,有一个简单的继承解决方案:
#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
,我想你只是想要A
中dosomething
的接口,你可以推导出来:
#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
得到什么以及应该做什么。
我正在寻找一种方法来提取 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, 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;
}
考虑 AAdapter 更改、添加或删除不透明参数类型的方式。基本上,我希望 B::doSomething()
简单地重定向到 B::AAdapter::doSomething()
,因此我希望这两种方法具有完全相同的签名。问题是:如何从 B
内部获取 B::AAdapter::doSomething()
的参数类型?
我在上面的代码中对 B::doSomething()
的定义是我最深入的:我正在使用 A
中的参数类型对 std::tuple
进行类型定义,所以我可以在 B
中将它们解压回参数包。不幸的是,使用上述方法我仍然需要在调用 B::doSomething()
时手动提供 Index...
模板参数。当然必须有一种方法可以让这些 Index...
参数自动从元组的大小中推导出来。我考虑过使用 std::make_integer_sequence
的方法,但这需要我为序列本身定义一个额外的方法参数(并且它不能是具有默认值的最后一个参数,因为在参数之后不允许其他参数包)。
有或没有 std::tuple 我有什么办法可以做到这一点吗?需要 C++17 的解决方案就可以了。
编辑 1:
我现在意识到我可以通过 B
inherit from AAdapter
而不是 [=26] 来绕过我的特定应用程序中的问题=]反对作为成员,但我仍然想知道如何解决问题而不必这样做。
编辑 2:
也许还有一些关于为什么 AAdapter
存在以及我想要实现的目标的额外信息。我正在围绕现有的 C API 实现一种包装器 class,它实际上需要在另一个进程中调用,RPC 样式。因此,如果用户想在远程进程中调用 C 函数,他们将在本地调用我的包装器 class 中的相应方法,该方法处理所有 RPC 内容,如类型转换、实际的远程调用和其他丑陋的细节。这个包装器 class 在我上面的代码中由 B
表示。现在我的包装器方法签名通常不会与 C 函数具有完全相同的签名。例如,包装器可能具有 std::string_view
而不是 C 函数具有的一对 const char*, size_t
。由于此处不重要的原因,它还 需要 有一个输出参数(指针),其中 C 函数有时具有 return 值。
为了让我不必定义两个单独的方法签名(实际上是三个)并编写代码来转换每个方法的参数,我只将其中一个签名作为模板参数传递 RetT, ArgsT...
到 B
。签名转换 class(上例中的 AAdapter
)然后应用有关如何通过添加参数、更改其类型等从第一个签名自动生成第二个签名的规则。A
然后将保存这个生成的签名,B
将拥有我最初提供的签名。但是,我希望 B
提供带有 A
签名的 invoke()
方法,从而对用户完全隐藏 A
和整个方法签名混乱。这就是为什么我需要从 B
中访问 A
的模板参数类型,以及为什么我不能简单地删除中间的 class AAdapter
.
这演示了如何从元组中获取具有参数类型的函数:
#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);
}
这会在 运行 时编译并打印“调用成功”。
The core of your problem is turning a tuple into an argument pack.
也许元组类型不是模板参数?在这种情况下,有一个简单的继承解决方案:
#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
,我想你只是想要A
中dosomething
的接口,你可以推导出来:
#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
得到什么以及应该做什么。