C++ - 通用编程 - 类型选择

发布于 2024-07-25 12:28:50 字数 1284 浏览 10 评论 0原文

以下片段返回 T 类型整数(假定无符号)的下一个最高的 2 次幂。它通过重复移位位来实现此目的。

出于所有意图和目的,位移循环中使用的无符号类型 i 足够大,足以表示(名义上)65536 位数字。 因此,实际上,保持“原样”就可以了。

template <class T>
T supremum_2(T k) {
  if (k == T(0)) return T(1);
  k--;  
  for (int i=1; i<sizeof(T)*8; i++) k |= k >> i;
  return k+1;
}

为了完成专业工作,应在编译时选择循环计数器的类型,以保证能够跨越 sizeof(T)*8 而不会溢出。

这可以在编译时使用 std::numeric_traits 完成吗? 如果是这样怎么办?

从概念上讲,我希望能够写出类似的内容:

typedef unsigned_type_that_can_represent<sizeof(T)*8> counter_type;

...
...

for (counter_type i(1); i<sizeof(T)*8; i<<=1) k = k | k >> i;
...

根据下面的讨论,我决定添加以下上下文。

换句话说:

我们如何保证在编译时为模板代码的内部工作选择高效(仅需要多大)且合适的类型? 如果我们发现自己在模板代码中使用具体类型,我们可能会通过潜在的不透明途径对模板的类型做出无意的假设。

例如,如果我们坚持使用 int 作为计数器,那么一切都会正常工作,直到有人将模板代码与他们的 bigint 库一起使用。 这可以表示比 int 可以表示的二进制位数更多的整数。 因此,我们应该将类型设置为 unsigned long long 吗? 当然,这只是推迟了问题的解决(尽管很长一段时间)? 此解决方案有“640K - 对于每个人来说足够大”或静态数组大小的阴影。

在这种情况下,显而易见但有些低效的选择是将计数器的类型设置为与数字 k 的类型相同。 它(原则上)效率低下,因为我们只要求计数器能够表示与 k 的位数相对应的数字。 对于其他情况,这种假设可能是错误的。

那么一般情况呢? 看起来元编程是一种合适的方法。 如何保持这种“理智”? 也许,形式上,要求是编译时函数将(可能派生的)抽象类型要求映射到类型。

也许这是 YABL(Yet Another Boost Library)的工作!

[抱歉跑题了]

The following fragment returns the next highest power of two for an (assumed unsigned) integer of type T. It does this by shifting the bits repeatedly.

For all intents and purposes the unsigned type i used in the bit shifting loop is sufficiently large to represent a (nominally) 65536 bit number. Practically therefore it's fine to leave it 'as is'.

template <class T>
T supremum_2(T k) {
  if (k == T(0)) return T(1);
  k--;  
  for (int i=1; i<sizeof(T)*8; i++) k |= k >> i;
  return k+1;
}

To do a professional job, the type of the loop counter should be chosen at compile time so that it guarantees to be able to span up to sizeof(T)*8 without overflow.

Can this be done at compile time using std::numeric_traits? If so how?

Conceptually I would like to be able to write something like:

typedef unsigned_type_that_can_represent<sizeof(T)*8> counter_type;

...
...

for (counter_type i(1); i<sizeof(T)*8; i<<=1) k = k | k >> i;
...

Based on the discussions below I have decided to add the following context.

Put another way:

How can we guarantee to select efficient (only as big as they need to be) and suitable types for the internal workings of template code at compile time? If we find ourselves using concrete types in template code we may be making inadvertent assumptions about the types of the templates through a potentially opaque route.

For example, if we were to stick with (say) an int for the counter, all will work fine until someone uses the template code with their bigint library. This could represent integers with more binary digits than can be represented by an int. Should we therefore make the type unsigned long long? Surely that just delays the problem (albeit for a long time)? There are shades of "640K - big enough for everybody" or static array sizes about this solution.

The obvious, but somewhat inefficient, choice in this instance is to set the type of the counter to be the same as the type of the number k. It is (in principle) inefficient since we only demand that the counter be able to represent a number corresponding to the number of bits of k. It may be that for other situations this is the wrong thing to assume.

What about the general case? It looks as though meta-programming is an appropriate approach. How to keep this 'sane'? Perhaps, formally, the requirement is for a compile-time function to map (potentially derived) abstract type requirements on to types.

Perhaps it's a job for YABL (Yet Another Boost Library)!

[Apologies for rambling]

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

黒涩兲箜 2024-08-01 12:28:51

您可以使用元编程:

template <bool g, typename T, typename E>
struct IF {
    typedef T RET;
};

template < typename T, typename E>
struct IF<false, T, E> {
    typedef E RET;
};

// if sizeof(int) < sizeof(long) then use long else use int
IF< sizeof(int)<sizeof(long), long, int>::RET i;

You can use meta proramming:

template <bool g, typename T, typename E>
struct IF {
    typedef T RET;
};

template < typename T, typename E>
struct IF<false, T, E> {
    typedef E RET;
};

// if sizeof(int) < sizeof(long) then use long else use int
IF< sizeof(int)<sizeof(long), long, int>::RET i;
生生漫 2024-08-01 12:28:51

这可以使用特征实现来完成。 像这样的事情:

// base type for template
template <class T>
struct counter_type
{
};

// specialisation for unsigned integer
template <>
struct  counter_type <unsigned>
{
  typedef unsigned long long value_type; // or whatever
};

template <class T>
T supremum_2(T k) {
  if (k == T(0)) return T(1);
  k--;
  /* Determined by repeated bit shifting. */
  for (counter_type<T>::value_type i=1; i<sizeof(T)*8; i<<=1) k = k | k >> i;
  return k+1;
}

如果我的语法错误,请原谅我,我总是必须查找模板语法。 总体思路就在这里。

This can be done using a traits implementation. Something like this:

// base type for template
template <class T>
struct counter_type
{
};

// specialisation for unsigned integer
template <>
struct  counter_type <unsigned>
{
  typedef unsigned long long value_type; // or whatever
};

template <class T>
T supremum_2(T k) {
  if (k == T(0)) return T(1);
  k--;
  /* Determined by repeated bit shifting. */
  for (counter_type<T>::value_type i=1; i<sizeof(T)*8; i<<=1) k = k | k >> i;
  return k+1;
}

Please forgive me if I get the syntax wrong, I always have to look up template syntax. The general idea is here.

開玄 2024-08-01 12:28:50

我相信您想将循环编写为

for (int i=1; i<sizeof(T)*8; i++) k |= k >> i;
return k+1;

int 至少可以存储最多 2^15-1 的值,这对此绰绰有余。 尽管如此,我还是这样做的,

template<int N, bool B8  = (N>8), 
                bool B16 = (N>16), 
                bool B32 = (N>32)>
struct select_t;

template<int N>
struct select_t<N, false, false, false> { typedef unsigned char type; };

template<int N>
struct select_t<N, true, false, false> { typedef unsigned short type; };

template<int N>
struct select_t<N, true, true, false> { typedef unsigned long type; };

int main() { select_t<32>::type i = 0; } // unsigned long

如果您的编译器恰好有该类型,您也可以使用 unsigned long long 来实现。

I believe you wanted to write your loop as

for (int i=1; i<sizeof(T)*8; i++) k |= k >> i;
return k+1;

An int can at least store values up to 2^15-1, which is more than enough for this. Nonetheless, here is how i do it

template<int N, bool B8  = (N>8), 
                bool B16 = (N>16), 
                bool B32 = (N>32)>
struct select_t;

template<int N>
struct select_t<N, false, false, false> { typedef unsigned char type; };

template<int N>
struct select_t<N, true, false, false> { typedef unsigned short type; };

template<int N>
struct select_t<N, true, true, false> { typedef unsigned long type; };

int main() { select_t<32>::type i = 0; } // unsigned long

You could do it with unsigned long long too, if you happen to have that type available in your compiler.

回首观望 2024-08-01 12:28:50

事实上,你是对的。

但实际上,如果你的 T 是 128 位,则移位操作的最高数量将是 127,恰好适合 char...

所以你是不是有点过度设计了循环变量类型? (可能是由于移位运算符 iso plain 增量,如 litb 所指出的)

In fact, you're right.

But in reality, if your T would be 128 bit, the highest number of shift operations would be 127, neatly fitting a char...

So aren't you over-engineering the loop variable type a bit? (May be due to the shift operator i.s.o. plain increment, as pointed out by litb)

小苏打饼 2024-08-01 12:28:50

检查静态断言,这可能是解决您的问题。

#include <climits>
#include <cwchar>
#include <limits>
#include <boost/static_assert.hpp>

namespace my_conditions {

   BOOST_STATIC_ASSERT(std::numeric_limits<int>::digits >= 32);
   BOOST_STATIC_ASSERT(WCHAR_MIN >= 0);

} // namespace my_conditions

Check up on static asserts, that might be a solution to your problem.

#include <climits>
#include <cwchar>
#include <limits>
#include <boost/static_assert.hpp>

namespace my_conditions {

   BOOST_STATIC_ASSERT(std::numeric_limits<int>::digits >= 32);
   BOOST_STATIC_ASSERT(WCHAR_MIN >= 0);

} // namespace my_conditions
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文