【问题标题】:C++ template functions accepting parameters in random orderC++ 模板函数以随机顺序接受参数
【发布时间】:2021-04-14 10:19:28
【问题描述】:

我正在编写一个 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 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

【问题讨论】:

  • stringstring 有何不同?请参阅第二次实例化。
  • 您可能对how-to-generate-all-the-permutations-of-function-overloads 感兴趣(实际上是 C++14,但可能在 C++11 中完成)。
  • @zdf 我的错,把它改成不同的类型
  • @Yksisarvinen 它是一个库,我需要实例化以便应用程序链接
  • 顺便说一句,您不需要在&lt;&gt; 中指定模板参数,因为有可推导。

标签: c++ c++11 templates


【解决方案1】:

如果您的目标是匹配给定库的 API 设计,最好的学习方法是深入研究其源代码并剖析它。

考虑一下这段代码(我仍然使用 CPR 作为示例,正如你提到的那样作为参考):

cpr::Session session;
session.SetOption(option1);
session.SetOption(option2);
session.SetOption(option3);

您需要一种可以处理 option1option2... 的方法,无论它们以何种顺序提供。随后对SetOption 的调用可以替换为单个SetOptions(option3, option1, option2)。因此我们需要一个 variadic 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 的任务。你可以找到一个简单的例子here

这里的关键是要有一个重载函数,它可以单独处理每个参数类型(CPR 中的example 带有SetOptions)。然后,在你的“permutable”函数中,为你的每个参数调用重载函数,一次一个(CPR 中的example,然后在variousplaces 中使用) .

需要注意的一点是,您可以传递多个相同类型的参数。根据您想要实现的目标,这可能是一个问题。

此外,您可以使用不受支持的参数类型(不匹配任何重载)调用该方法,在这种情况下,错误消息并不总是明确的,具体取决于您使用的编译器。但是,这是您可以使用static_asserts 克服的问题。

【讨论】:

  • 这太棒了 - 考虑到了我问题的各个方面。谢谢!
  • 如何实例化以便应用链接?
  • 假设您有一个这样的模板:template &lt;typename T&gt; foo(T arg) { bar(arg); }。如果在您的代码中使用intstd::string 调用foo,则需要定义bar 的两个重载:一个接受int(或任何可以隐式转换的类型)来自int)和std::string(或任何可以隐式转换的类型)。如果你尝试用任何其他类型调用foo——比如说std::vector&lt;int&gt;——并且bar没有接受std::vector&lt;int&gt;的重载,那么你就有问题了。您需要定义一个新的 bar 重载来修复它。
【解决方案2】:

除了必须为每种可能性手动包含代码之外,有没有办法实例化模板以接受任何随机顺序的参数类型?

对于没有宏的显式实例化定义,您无法执行此操作,但您可以使用单独的方法并依赖隐式实例化,使用 SFINAE 根据两个自定义特征来限制主模板(您将其定义移至头文件) .

首先,给定以下类型序列

template <class... Ts>
struct seq {};

我们想要构造一个特征,对于给定的类型序列seq&lt;T1, T2, ...&gt;(你的“10 个参数类型”),表示为s

  • s 应该是您选择的seq&lt;AllowedType1, ...&gt; 类型集的子集,并且
  • 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
}

【讨论】:

    【解决方案3】:

    为什么不在这里使用构建器模式呢?您将创建一个带有各种 setXxx 方法的 foo_builder 和一个最终的 build() 以获取完全配置的对象。

    【讨论】:

      【解决方案4】:

      使用结构来保存所有参数。

      namespace foo
      {
          struct do_params {
              int a;
              long b;
              std::string c;
          };
          void do(do_params params);
      };
      

      【讨论】:

      • 这并没有解释如何允许随机参数顺序(无论如何不可能使用该代码)或如何在库的上下文中链接
      猜你喜欢
      • 2021-10-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-18
      • 2021-03-29
      • 2011-03-24
      • 2012-08-16
      • 2015-11-10
      相关资源
      最近更新 更多