使用多个参数包将运行时值转换为编译模板参数

时间:2016-05-09 17:14:32

标签: c++ templates variadic-templates

在我第一次尝试使用可变参数模板时,我正在尝试创建一个函数,该函数使用模板参数的所有组合来实例化派生类。拥有运行时选项类的想法是能够选择要使用的确切模板实例化。我已经能够使用基础案例来处理1和2模板参数。但是一般情况不起作用,下面的SelectInstance的第三次调用无法编译,出现以下错误:

  

候选函数不可行:需要3个参数,但提供了2个参数   AbstractBase * SelectInstance(Func func,Args ... args)

完整的代码如下:

#include <memory>
#include <iostream>

struct Options
{
  bool GetFirstParameter() { return true; }
  bool GetSecondParameter() { return false; }
  bool GetThirdParameter() { return true; }
};

struct AbstractBase
{
  virtual void PrintMe() = 0;
};

template<class... Args>
struct Derived : public AbstractBase
{
  virtual void PrintMe() { std::cout << __PRETTY_FUNCTION__ << std::endl; }
};

template<class... Args>
AbstractBase *SelectInstance()
{
  return new Derived<Args...>();
}

template<class Func, class... UnpackedArgs>
AbstractBase *SelectInstance(Func func)
{
  if (func())
    return SelectInstance<float, UnpackedArgs...>();
  else
    return SelectInstance<double, UnpackedArgs...>();
}

template<class Func, class... Args, class... UnpackedArgs>
AbstractBase *SelectInstance(Func func, Args... args)
{
  if (func())
    return SelectInstance<Args...,  float, UnpackedArgs...>(args...);
  else
    return SelectInstance<Args..., double, UnpackedArgs...>(args...);
}

int main()
{
  Options opts;
  std::unique_ptr<AbstractBase> one(
    SelectInstance(
      std::bind(&Options::GetFirstParameter, &opts)
      )
    );
  one->PrintMe();

  std::unique_ptr<AbstractBase> two(
    SelectInstance(
      std::bind(&Options::GetFirstParameter, &opts),
      std::bind(&Options::GetSecondParameter, &opts)
      )
    );
  two->PrintMe();

  // this one fails to compile!
  std::unique_ptr<AbstractBase> three(
    SelectInstance(
      std::bind(&Options::GetFirstParameter, &opts),
      std::bind(&Options::GetSecondParameter, &opts),
      std::bind(&Options::GetThirdParameter, &opts)
      )
    );
  three->PrintMe();
}

我可能会错误地使用可变参数模板将仿函数转换为新的参数包。任何指导表示赞赏。

1 个答案:

答案 0 :(得分:1)

您的尝试失败的原因是编译器无法分辨您提供的参数。假设我们的函数类型为ABC,为简单起见。最初的电话是:

// Func = A, Args... = {B, C}, UnpackedArgs... = {}
AbstractBase *SelectInstance(Func func, Args... args)

从这里,我们致电SelectInstance<Args..., float, UnpackedArgs...>(args...);也就是说,SelectInstance<B, C, float>(b, c);您的意图是:

// Func = B, Args... = {C}, UnpackedArgs... = {float}
AbstractBase *SelectInstance(Func func, Args... args)

但真正的参数包是贪婪的,并采取一切。 Args...获取您明确提供的每个后续类型参数。所以这个电话真的被解释为:

// Func = B, Args... = {C, float}, UnpackedArgs... = {}
AbstractBase *SelectInstance(Func func, Args... args)

这个函数有三个参数(一个B,一个C和一个float),但你只传递两个(bc ),因此错误。

要解决此问题,请翻转模板参数的顺序,先将推导出的参数放入,然后推断出推断的参数。这样,您仍然可以使用模板推导来做正确的事情,甚至避免额外的过载:

template<class... Args>
AbstractBase *SelectInstance()
{
  return new Derived<Args...>();
}

template<class... UnpackedArgs, class Func, class... Args>
AbstractBase *SelectInstance(Func func, Args... args)
{
  if (func()) {
    return SelectInstance<float, UnpackedArgs...>(args...);
  }
  else {
    return SelectInstance<double, UnpackedArgs...>(args...);
  }
}

这是有效的,因为现在您提供的所有参数都会进入UnpackedArgs...,而演绎会计算出FuncArgs... - 这正是您想要的。