繁体   English   中英

C++ 模板函数以随机顺序接受参数

[英]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);

您需要一种可以处理option1option2...的方法,无论它们以何种顺序提供。 随后对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 &param_a, const U &param_b, const V &param_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 &param_a, const U &param_b, const V &param_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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM