模板:在运行时从(并引用)非类型参数实例化?

时间:2010-08-10 21:51:45

标签: c++ templates selection instantiation

我开发了一个通用的“Unsigned”类,或者实际上是一个类模板Unsigned<size_t N>,它使用uint8_t s作为参数,在C(C ++)内置unsigned之后进行建模。例如,Unsigned<4>uint32_t相同,Unsigned<32>uint256_t相同 - 如果存在的话。{/ p>

到目前为止,我已经成功地遵循了内置无符号所需的大部分(如果不是全部)语义 - 特别是sizeof(Natural<N>)==N(Natural<N>(-1) == "max_value_all_bits_1" == ~Natural<N>(0)),与abs(),sign()的兼容性, div(使用自定义div_t结构),ilogb()(看起来不包括GCC)和numeric_limits&lt;&gt;。

但是我面临的问题是,因为1.-类模板只是一个模板,因此模板形式是不相关的,而2.-模板非类型参数需要一个“编译时常量”,这是比“const更严格”,我基本上无法创建一个未知的N

换句话说,我不能有这样的代码:

...
( ... assuming all adequate headers are included ...)
using namespace std;
using lpp::Unsigned;
std::string str;
cout<< "Enter an arbitrarily long integer (end it with <ENTER>) :>";
getline(cin, str, '\n'); 
const int digits10 = log10(str.length()) + 1; 
const int digits256 = (digits10 + 1) * ceil(log(10)/log(256)); // from "10×10^D = 256^T"
// at this point, I "should" be able to, semantically, do this:
Unsigned<digits256> num; // <-- THIS I CAN'T -- num would be guaranteed 
                         // big enough to hold str's binary expression, 
                         // no more space is needed
Unsigned::from_str(num, str); // somehow converts (essentially a base change algo)
// now I could do whatever I wanted with num "as if" a builtin.
std::string str_b3 = change_base(num, 3); // a generic implemented somehow
cout<< "The number above, in base 3, is: "<< str_b3<< endl;
...

(A / N - 这是Unsigned的测试套件的一部分,它读取“略大的数字”(我已经尝试了多达120位数 - 在相应地设置N之后)并做了类似的事情在其他基础上表达,它本身已经测试了所有算术函数。)

在寻找绕过或以其他方式缓解这种限制的可能方法时,我已经遇到了一些我想尝试和探索的概念,但我不想花太多精力去做另一种选择。会使事情变得更复杂,或者会使阶级的行为偏离太多。

我想到的第一件事是,如果我无法选择Unsigned<N>我的选择,我至少可以从一组预先选定的N值中选择,这将导致在运行时调用足够的构造函数,但取决于编译时值:

???? GetMeAnUnsigned (size_t S) {
  switch (S) {
    case 0: { throw something();  } // we can't have a zero-size number, right?
    case 1, 2, 3, 4: { return Unsigned<4>(); break; }
    case 5, 6, 7, 8: { return Unsigned<8>(); break; }
    case 9, 10, 11, 12, 13, 14, 15, 16: { return Unsigned<16>(); break; }
    ....
    default: { return Unsigned<128>(); break; } // wow, a 1Kib number!
    } // end switch
  exit(1); // this point *shouldn't* be reachable!
  } // end function

我个人喜欢这种方法。但是我不知道我可以用什么来指定返回类型。它实际上并没有“解决”问题,它只会在一定程度上降低其严重性。我确信使用交换机可以正常工作,因为实例化 来自编译时常量,它只会更改它们将发生的

声明返回类型的唯一可行帮助似乎是这个新的C ++ 0(1?)X“ decltype ”构造,这将允许我获得足够的类型,如,如果我正确理解了这个特征:

decltype (Unsigned<N>) GetMeAnUnsigned (size_t S) {
  .. do some choices that originate an N
  return Unsigned<N>();
  }

......或类似的东西。我还没有进入C ++?X auto(对于迭代器),所以第一个问题是:decltypeauto这样的功能帮我实现了什么我想要吗?运行时选择实例化,即使有限)

另一方面,我想如果问题是我的类之间的关系,那么我可以通过派生模板本身使它们 all 成为一种“类型”Base:

template <size_t N>
class Unsigned : private UnsignedCommon { ...

...但是我把这种方法留在了后备者身上,因为,好吧,一个人没有那个(使所有人都成为“亲切的”)与内置插件,加上案件其中一个 实际上将它们视为一个公共类,它需要初始化静态,返回指针并让客户端破坏,如果我没记错的话。第二个问题:我是否过早地放弃了这个替代方案?

4 个答案:

答案 0 :(得分:2)

简而言之,您的问题与内置整数类型的问题没有什么不同。给定short,您无法在其中存储大整数。并且您不能在运行时决定使用哪种类型的整数,除非您使用switch或类似的选项来选择多个预定义选项(shortintlong ,例如long long。或者在您的情况下,Unsigned<4>Unsigned<8>Unsigned<256>。无法在运行时动态计算大小,任何方式。

您必须定义动态大小的类型(类似于std::vector),其中不是模板参数的大小,以便单个类型可以存储 any < / em>整数类型(然后接受所暗示的效率损失),或者接受必须在编译时选择大小,并且处理“任意”整数的唯一选择是硬编码一组预定义的大小并在运行时在它们之间进行选择。

decltype也无法解决您的问题。它与auto非常相似,它完全在编译时工作,只返回表达式的类型。 (2+2的类型是int,并且编译器在编译时知道这一点,即使值4仅在运行时计算)

答案 1 :(得分:1)

您面临的问题非常普遍。模板在编译时解析,而您需要在运行时更改行为。尽管你可能希望用神秘的一个额外的间接层来解决这个问题,但问题不会消失:你不能选择函数的返回类型。

由于您需要根据运行时信息执行操作,因此必须回退到使用动态多态(而不是模板提供的静态多态)。这意味着在GetMeAnUnsigned方法中使用动态分配并可能返回指针。

您可以使用一些技巧,例如将指针隐藏在提供公共接口的类中并委托给内部分配的对象,其格式与boost::any相同,以便用户看到单个类型即使在运行时选择了实际对象。这将使设计更难,我不确定代码会有多复杂,但您需要真正考虑在内部类层次结构中必须提供的最小接口是什么,以满足外部接口的要求 - 这似乎是一个非常有趣的问题......

答案 2 :(得分:0)

你不能直接这样做。每个带有单独数字的unsigned都有一个单独的类型,编译器需要在编译时知道方法的返回类型。

您需要做的是拥有一个Unsigned_base基类,Unsigned<t>个项目从中派生。然后,您可以让GetMeAnUnsigned方法返回指向Unsigned_base的指针。然后可以使用类似dynamic_cast<Unsigned<8> >()的内容进行投射。

你可能最好让你的函数返回可能的unsigned<n>类型的并集,但只有当你的类型满足成为工会成员的要求时才会起作用。

编辑:这是一个例子:

struct UnsignedBase
{
    virtual ~UnsignedBase() {}
};

template<std::size_t c>
class Unsigned : public UnsignedBase
{
    //Implementation goes here.
};

std::auto_ptr<UnsignedBase> GiveMeAnUnsigned(std::size_t i)
{
    std::auto_ptr<UnsignedBase> result;
    switch(i)
    {
    case 42:
        result.reset(new Unsigned<23>());
    default:
        result.reset(new Unsigned<2>());
    };
    return result;
}

答案 3 :(得分:0)

这确实是一个非常常见的问题,上次我看到它是使用矩阵(维度作为模板参数以及如何处理运行时提供的值)。

不幸的是,这是一个棘手的问题。

这个问题并不是特定于C ++本身,它特定于强类型和编译时检查。例如,Haskell可能表现出类似的行为。

有两种方法可以解决这个问题:

  • 您使用switch不创建类型,但实际上是为了启动完整计算,即main几乎为空,只用于读取输入值
  • 您使用装箱:您将实际类型放在通用容器中(通过手工制作的类或boost::anyboost::variant),然后在必要时取消特定处理的值。< / LI>

我个人更喜欢第二种方法。

更简单的方法是使用基类(接口):

struct UnsignedBase: boost::noncopyable
{
  virtual ~UnsignedBase() {}
  virtual UnsignedBase* clone() const = 0;

  virtual size_t bytes() const = 0;

  virtual void add(UnsignedBase const& rhs) = 0;
  virtual void substract(UnsignedBase const& rhs) = 0;
};

然后,您将此类包装在一个简单的管理器中,以简化客户端的内存管理(隐藏您依赖堆分配+ unique_ptr的事实):

class UnsignedBox
{
public:
  explicit UnsignedBox(std::string const& integer);

  template <size_t N>
  explicit UnsignedBox(Unsigned<N> const& integer);

  size_t bytes() const { return mData->bytes(); }

  void add(UnsignedBox const& rhs) { mData->add(rhs.mData); }
  void substract(UnsignedBox const& rhs) { mData->substract(rhs.mData); }

private:
  std::unique_ptr<UnsignedBase> mData;
};

这里,虚拟调度负责取消装箱(有些),您也可以使用dynamic_cast手动取消装箱(如果您知道位数,则可以使用static_cast):

void func(UnsignedBase* i)
{
  if (Unsigned<2>* ptr = dynamic_cast< Unsigned<2> >(i))
  {
  }
  else if (Unsigned<4>* ptr = dynamic_cast< Unsigned<4> >(i))
  {
  }
  // ...
  else
  {
    throw UnableToProceed(i);
  }
}