[英]C++ template functions accepting parameters in random order
我正在编写一个 C++ 网络库,并希望主(模板)function 以随机顺序接受参数,使其更加用户友好,就像CPR库一样。
模板 function 最多可以同时接受 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 U, typename V>
void foo::do(const T& param_a, const U& param_b, 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, std::string, int>(const int&, const std::string&, 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... */ }
问题是“如何为ts
参数包中的每个项目调用SetOption
?”。 这是std::initializer_list
的任务。 你可以在这里找到一个简单的例子。
这里的关键是有一个重载的 function可以单独处理每个参数类型( 例如在 CPR 中使用 SetOptions)。 然后,在你的“permutable” function 中,你为每个arguments 调用重载的 function ,然后在不同的地方使用一个( 例如在 CPR 中)
不过要注意的一件事是,您可以传递相同类型的多个参数。 根据您想要实现的目标,这可能是一个问题,也可能不是。
此外,您可以使用不受支持的参数类型(不匹配任何重载)调用该方法,在这种情况下,错误消息并不总是明确的,具体取决于您使用的编译器。 但是,这是您可以使用static_assert
克服的问题。
有没有办法实例化模板以接受任何随机顺序的参数类型,而不是必须手动包含每种可能性的代码?
对于没有宏的显式实例化定义,您无法执行此操作,但您可以使用单独的方法并依赖隐式实例化,使用 SFINAE 来限制基于两个自定义特征的主模板(您将其定义移至 header 文件)。
首先,给定以下类型序列
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, i, s); // OK
foo::doStuff(l, s, i); // OK
// uniqueness
foo::doStuff(l, 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, typename U, typename V, typename Seq = seq<T, U, V>,
typename = std::enable_if_t<is_subset_of_v<Seq, detail::MyAllowedTypeSeq>>>
void do(const T ¶m_a, const U ¶m_b, const V ¶m_c) {
// do lots of stuff
}
为什么不在这里使用构建器模式? 您将使用各种setXxx
方法和最终的build()
创建一个foo_builder
以获得完全配置的 object。
使用结构来保存所有参数。
namespace foo
{
struct do_params {
int a;
long b;
std::string c;
};
void do(do_params params);
};
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.