如何解决C++ 模板函数以随机顺序接受参数
我正在编写一个 C++ 网络库,并希望主(模板)函数以随机顺序接受参数,使其更加用户友好,就像 CPR图书馆可以。
模板函数最多可同时接受 10 个参数,每个参数类型不同。有没有办法实例化模板以接受参数类型的任何随机顺序,而不是必须手动包含每种可能性的代码?
例如 - 在这种情况下使用 3 个参数,每个参数的类型不同:
.h 文件
namespace foo
{
template <typename T,typename U,typename V> void do(const T& param_a,const U& param_b,const V& param_c);
};
.cpp 文件
template <typename T,typename V>
void foo::do(const T& param_a,const V& param_c) {
//do lots of stuff
}
//instantiate to allow random param order
template void foo::do<int,std::string,long>(const int&,const std::string&,const long&);
template void foo::do<int,long,std::string>(const int&,const long&,const std::string&);
template void foo::do<int,int>(const int&,const int&);
//etc... to cover all possible param orders
解决方法
如果您的目标是匹配给定库的 API 设计,最好的学习方法是深入研究其源代码并对其进行剖析。
考虑这段代码(我仍然使用 CPR 作为示例,因为您提到它作为参考):
cpr::Session session;
session.SetOption(option1);
session.SetOption(option2);
session.SetOption(option3);
您需要一个可以处理 option1
,option2
,...
的方法,无论它们以何种顺序提供。对 SetOption
的后续调用可以替换为单个 SetOptions(option3,option1,option2)
。因此我们需要一个可变参数 SetOptions
方法:
template<typename Ts...> // important: don't specialize the possible argument types here
void SetOptions(Ts&&... ts)
{ /* do something for each param in ts... */ }
问题是“您如何为 SetOption
参数包中的每个项目调用 ts
?”。这是std::initializer_list
的使命。您可以找到一个简单的示例 here。
这里的关键是有一个重载函数,它可以单独处理每个参数类型(example 在 CPR 中使用 SetOptions)。然后,在您的“可变”函数中,您为每个参数调用重载函数,一次一个(CPR 中的 example,然后在 various places).
需要注意的一点是,您可以传递多个相同类型的参数。根据您想要实现的目标,这可能是一个问题,也可能不是。
此外,您可以使用不受支持的参数类型(不匹配任何重载)调用该方法,在这种情况下,错误消息并不总是明确的,具体取决于您使用的编译器。然而,这是您可以使用 static_assert
克服的问题。
有没有办法实例化模板以接受参数类型的任何随机顺序,而不是必须手动包含每种可能性的代码?
对于没有宏的显式实例化定义,您不能这样做,但是您可以使用单独的方法并依赖隐式实例化,使用 SFINAE 基于两个自定义特征来限制主模板(您将其定义移至头文件) .
首先,给定以下类型序列
template <class... Ts>
struct seq {};
我们想要构造一个特征,对于给定的类型序列 seq<T1,T2,...>
(您的“10 个参数类型”),表示为 s
:
-
s
应是您选择的一组类型的子集seq<AllowedType1,...>
,并且 -
s
应仅包含唯一类型。
我们可以将前者实现为:
#include <type_traits>
template <class T,typename... Others>
constexpr bool is_same_as_any_v{(std::is_same_v<T,Others> || ...)};
template <typename,typename> struct is_subset_of;
template <typename... Ts,typename... Us>
struct is_subset_of<seq<Ts...>,seq<Us...>> {
static constexpr bool value{(is_same_as_any_v<Ts,Us...> && ...)};
};
template <typename T,typename U>
constexpr bool is_subset_of_v{is_subset_of<T,U>::value};
后者为
template <typename...> struct args_are_unique;
template <typename T> struct args_are_unique<T> {
static constexpr bool value{true};
};
template <typename T,typename... Ts> struct args_are_unique<seq<T,Ts...>> {
static constexpr bool value{!is_same_as_any_v<T,Ts...> &&
args_are_unique<seq<Ts...>>::value};
};
template <typename... Ts>
constexpr bool args_are_unique_v{args_are_unique<Ts...>::value};
之后我们可以将主模板定义为
namespace foo {
namespace detail {
using MyAllowedTypeSeq = seq<int,long,std::string>; // ...
} // namespace detail
template <
typename T,typename U,typename V,typename Seq = seq<T,U,V>,typename = std::enable_if_t<is_subset_of_v<Seq,detail::MyAllowedTypeSeq> &&
args_are_unique_v<Seq>>>
void doStuff(const T ¶m_a,const U ¶m_b,const V ¶m_c) {
// do lots of stuff
}
} // namespace foo
以及我们可以和不可以使用主模板重载的地方,如下所示:
int main() {
std::string s{"foo"};
int i{42};
long l{84};
foo::doStuff(s,i,l); // OK
foo::doStuff(s,l,i); // OK
foo::doStuff(l,s); // OK
foo::doStuff(l,s,i); // OK
// uniqueness
foo::doStuff(l,i); // Error: candidate template ignored
// wrong type
unsigned int ui{13};
foo::doStuff(s,ui,l); // Error: candidate template ignored
}
如果类型实际上不需要是唯一的(从问题中有点不清楚),您可以简单地 SFINAE-仅在第一个 is_subset_of_v
特征上约束主模板:
template <
typename T,detail::MyAllowedTypeSeq>>>
void do(const T ¶m_a,const V ¶m_c) {
// do lots of stuff
}
,
为什么不在这里使用构建器模式?您将使用各种 foo_builder
方法和最终的 setXxx
创建一个 build()
以获取完全配置的对象。
使用结构体保存所有参数。
namespace foo
{
struct do_params {
int a;
long b;
std::string c;
};
void do(do_params params);
};
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。