【问题标题】:Variadic template parameter in middle of parameter list参数列表中间的可变模板参数
【发布时间】:2018-11-19 02:06:33
【问题描述】:

我正在尝试指定一个将泛型函数作为参数的函数。函数定义如下:

template <typename TRet, typename... TsArgs>
using Fun = TRet (*)(TsArgs...);

如何指定一个以该泛型函数为参数的泛型函数?我试过这个:

template<typename TRet, typename... TsArgs, Fun<TRet, TsArgs...> F>
TRet wrap(TsArgs... args) {
  return F(args...);
}

包装这个函数:

bool foo(int x, double y) {
    return x < y;
}

像这样:

Fun<bool, int, double> func = wrap<bool, int, double, foo>;

但是,不幸的是,这并不能编译。 gcc 8.1 有以下错误信息:

<source>:16:35: error: no matches converting function 'wrap' to type 'Fun<bool, int, double>' {aka 'bool (*)(int, double)'}
     Fun<bool, int, double> func = wrap<bool, int, double, foo>;
                                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~

而clang 6出现如下错误:

<source>:16:35: error: address of overloaded function 'wrap' does not match required type 'bool (int, double)'
    Fun<bool, int, double> func = wrap<bool, int, double, foo>;
                                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~

但是,如果我用int, double 替换TsArgs,就像foo() 的签名一样,它编译得很好,让我相信在参数列表中间有这些可变参数模板参数不会按我的预期工作。否则我怎么能实现我的目标?

这是 MCVE:

template <typename TRet, typename... TsArgs>
using Fun = TRet (*)(TsArgs...);

template<typename TRet, typename... TsArgs, Fun<TRet, TsArgs...> F>
TRet wrap(TsArgs... args) {
    return F(args...);
}

bool foo(int x, double y) {
    return x < y;
}

int main() {
    Fun<bool, int, double> func = wrap<bool, int, double, foo>;
    return 0;
}

【问题讨论】:

  • 你的怀疑是正确的。可变模板参数消耗模板的所有参数,如果没有在函数签名中明确指定,几乎无法推断。你必须找到其他方法来做你想做的事。
  • 我能想到的最佳解决方法是展开每个函数参数数量的可变参数。你知道什么更好的吗?
  • template &lt;auto F&gt; 可能很有用,但如果您想使用重载函数集的成员作为模板参数,这将变得很痛苦。

标签: c++ c++11 templates variadic-templates template-meta-programming


【解决方案1】:

如果你可以使用C++17,你可以使用auto作为模板参数。

这可以大大简化我之前的回答。

或者更好:简化包装器的使用。

如果你定义一个wrapHelper结构如下

template <typename T, T>
struct wrapHelper;

template <typename TRet, typename... TsArgs, Fun<TRet, TsArgs...> F>
struct wrapHelper<Fun<TRet, TsArgs...>, F>
 {
   static TRet func (TsArgs ... args)
    { return F(args...); }
 };

你可以写wrapper,使用auto,如下

template <auto X>
struct wrap : public wrapHelper<decltype(X), X>
 { };

这样就不需要解释返回类型(TRet)和参数类型(TsArgs...):它们是从foo推导出来的(在wrapHelper中)。

所以,而不是

Fun<bool, int, double> func = wrap<bool, int, double>::func<foo>;

你必须写

Fun<bool, int, double> func = wrap<foo>::func;

也可以

auto func = wrap<foo>::func;

以下是完整的编译示例

#include <iostream>
#include <type_traits>

template <typename TRet, typename... TsArgs>
using Fun = TRet (*)(TsArgs...);

bool foo(int x, double y)
 { return x < y; }

template <typename T, T>
struct wrapHelper;

template <typename TRet, typename... TsArgs, Fun<TRet, TsArgs...> F>
struct wrapHelper<Fun<TRet, TsArgs...>, F>
 {
   static TRet func (TsArgs ... args)
    { return F(args...); }
 };

template <auto X>
struct wrap : public wrapHelper<decltype(X), X>
 { };

int main()
 {   
   auto func { wrap<foo>::func };

   static_assert( std::is_same<decltype(func), Fun<bool, int, double>>{} );

   std::cout << func(1, 2.0) << std::endl;
 }

【讨论】:

  • 看起来很棒!我还添加了template&lt;auto X&gt; decltype(X) wrap_func = wrap&lt;X&gt;::func;,所以我可以使用wrap_func&lt;foo&gt;。我在想第一个解决方案也可以通过某种方式使用类型推导来简化,但我无法让它工作,所以我认为这是最好的
  • @Felk - 是的,wrap_func 在我看来是一个不错的改进。无论如何,关于第一个解决方案中的类型推导,我不能以合理的方式排除它是可能的(C++ 17 之前)。但我不知道怎么做。
【解决方案2】:

也许您可以分两步制作wrap:(1) 接收TRetTArgs... 模板参数的模板结构和(2) 接收Fun&lt;TRet, TsArgs...&gt; 模板参数的模板静态func() 函数.

如下

template <typename TRet, typename... TsArgs>
struct wrap
 {
   template <Fun<TRet, TsArgs...> F>
   static TRet func (TsArgs ... args)
    { return F(args...); }
 };

你可以这样使用

Fun<bool, int, double> func = wrap<bool, int, double>::func<foo>;

以下是一个完整的工作示例

#include <iostream>

template <typename TRet, typename... TsArgs>
using Fun = TRet (*)(TsArgs...);

template <typename TRet, typename... TsArgs>
struct wrap
 {
   template <Fun<TRet, TsArgs...> F>
   static TRet func (TsArgs ... args)
    { return F(args...); }
 };

bool foo(int x, double y)
 { return x < y; }

int main()
 {
   Fun<bool, int, double> func = wrap<bool, int, double>::func<foo>;

   std::cout << func(1, 2.0) << std::endl;
 }

【讨论】:

  • 如此简单,却又如此天才。这样就可以了。明天实施您的解决方案后,我将接受您的回答
  • @Felk - 没那么简单:通过这种方式,我们必须明确属于foo()TRetTsArgs... 类型。不幸的是,在 C++17 之前,我不知道如何解决这个问题。但是如果你可以使用 C++17 就可以了,wrap 的使用变得非常简单。使用 C++17 解决方案添加了另一个答案。
猜你喜欢
  • 2016-12-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-14
  • 2014-04-21
  • 1970-01-01
  • 1970-01-01
  • 2013-09-22
相关资源
最近更新 更多