是否可以使用模板元编程创建和初始化值数组?

时间:2010-02-09 02:04:03

标签: c++ metaprogramming

我希望能够在编译时使用模板元编程创建一个计算值数组(为了简单起见,我希望每个值都是它的索引的平方)。这可能吗?如何初始化数组中的每个位置?

(是的,有更简单的方法可以在不使用模板元编程的情况下执行此操作,只是想知道是否可以使用数组执行此操作。)

7 个答案:

答案 0 :(得分:14)

虽然您无法像这样初始化数组,但您可以通过创建递归struct 来执行几乎相同的操作:

template <int I>
struct squared {
    squared<I - 1> rest;
    int x;
    squared() : x((I - 1) * (I - 1)) {}
};

template <>
struct squared<1> {
    int x;
    squared() : x(0) {}
};

然后在您的代码中,您可以声明:

squared<5> s;

并且编译器确实会创建一个包含5 struct s {0}的int:0,1,4,16,25。

几点说明:

  1. 我对C ++标准的解释是,它没有保证这个struct将与数组相同的布局。虽然它是POD类型,但POD类型保证在内存(1.8 / 5)中“连续”布局,第一个成员位于偏移0(9.2 / 17),后来成员位于更高地址(9.2 / 12),并且数组也是“连续”布局的(8.3.4 / 1),标准并未说明数组与布局兼容具有此类struct s。但是,任何理智的编译器都会以相同的方式放置这些对象。 [编辑:正如ildjarn所指出的,用户定义的构造函数的存在实际上使这个类非聚合,因此非POD。同样,任何理智的编译器都不允许这会影响其布局。]
  2. C ++要求即使空struct长至少1个字节。如果没有,我们可以使用稍微更清晰的公式,其中递归的基本情况是I == 0,我们没有从I中减去1进行计算。
  3. 如果我们可以将struct放在具有适当大小的数组的union内,以便轻松访问成员,那就太好了。不幸的是,如果该对象具有非平凡的构造函数,C ++会禁止您在union中包含对象。因此,获得i元素的最简单方法是使用老式的演员:

    squared<5> s;
    cout << "3 squared is " << reinterpret_cast<int*>(&s)[3] << endl;
    

    如果您愿意,可以编写一个重载的operator[]()函数模板,以使其更漂亮。

答案 1 :(得分:14)

在元编程中称为Static Table Generation

#include <iostream>

const int ARRAY_SIZE = 5;

template <int N, int I=N-1>
class Table : public Table<N, I-1>
{
public:
    static const int dummy;
};

template <int N>
class Table<N, 0>
{
public:
    static const int dummy;
    static int array[N];
};

template <int N, int I>
const int Table<N, I>::dummy = Table<N, 0>::array[I] = I*I + 0*Table<N, I-1>::dummy;

template <int N>
int Table<N, 0>::array[N];

template class Table<ARRAY_SIZE>;

int main(int, char**)
{
    const int *compilerFilledArray = Table<ARRAY_SIZE>::array;
    for (int i=0; i < ARRAY_SIZE; ++i)
        std::cout<<compilerFilledArray[i]<<std::endl;
}

我们使用显式模板实例化和虚拟变量来强制编译器用索引方块填充数组。 I * I之后的部分是递归分配每个数组元素所需的技巧。

答案 2 :(得分:13)

在c ++ 0x中可以使用可变参数模板。以下是如何创建二项式系数表的示例:

//typedefs used
typedef short int              index_t;
typedef unsigned long long int int_t;

//standard recursive template for coefficient values, used as generator
template <index_t n, index_t k> struct coeff {static int_t const value = coeff<n-1, k-1>::value + coeff<n-1, k>::value;};
template <index_t n>            struct coeff<n, 0> {static int_t const value = 1;};
template <index_t n>            struct coeff<n, n> {static int_t const value = 1;};

//helper template, just converts its variadic arguments to array initializer list
template<int_t... values> struct int_ary {static int_t const value[sizeof...(values)];};
template<int_t... values> int_t const int_ary<values...>::value[] = {values...};

//decrement k, pile up variadic argument list using generator
template<index_t n, index_t k, int_t... values> struct rec: rec<n, k-1, coeff<n, k-1>::value, values...> {};
//when done (k == 0), derive from int_ary
template<index_t n,            int_t... values> struct rec<n, 0, values...>: int_ary<values...> {};

//initialise recursion
template<index_t n> struct binomial: rec<n, n+1> {};

要访问元素,请使用二项式&lt; N&gt; :: value [k]等语法,其中N是编译时常量,k是从0到N的索引。

答案 3 :(得分:4)

您可以使用固定大小的数组:

int a[] = { foo<0>::value, foo<1>::value, ... };

如果没有预处理器元编程,无法完成任意大小的数组 - Boost.Preprocessor可以在这里提供帮助。

您可能想要研究的另一件事是积分常数的编译时序列,例如:使用Boost.MPL

template<int n>
struct squares {
    typedef typename squares<n-1>::type seq;
    typedef typename boost::mpl::integral_c<int, n*n>::type val;    
    typedef typename boost::mpl::push_back<seq, val>::type type;
};

template<>
struct squares<1> {
    typedef boost::mpl::vector_c<int, 1>::type type;
};

// ...
typedef squares<3>::type sqr;
std::cout << boost::mpl::at_c<sqr, 0>::type::value << std::endl; // 1
std::cout << boost::mpl::at_c<sqr, 1>::type::value << std::endl; // 4
std::cout << boost::mpl::at_c<sqr, 2>::type::value << std::endl; // 9

(请注意,使用MPL算法可以更优雅地完成此操作)

如果您对编译时主题更感兴趣,可以阅读“Modern C ++ Design”(TMP基础知识)和“C ++ Template Metaprogramming”(主要是MPL解释) (深入)值得研究。

答案 4 :(得分:4)

我真的很喜欢j_random_hackers回答 - 它美丽而短暂。使用C ++ 11,可以将其扩展到

template <int I>
struct squared {
  squared<I - 1> rest;
  static const int x = I * I ;
  constexpr int operator[](int const &i) const { return (i == I ?  x : rest[i]); }
  constexpr int size() const { return I; }
};

template <>
struct squared<0> {
  static const int x = 0;
  constexpr int operator[](int const &i) const { return x; }
  constexpr int size() const { return 1; }
};

此代码在运行时可用

  squared<10> s;

  for(int i =0; i < s.size() ; ++i) {
    std::cout << "s[" << i << "]" << " = " << s[i] << std::endl;
  }

以及编译时间

struct Foo {
  static const squared<10> SquareIt;
  enum { 
    X = 3,
    Y = SquareIt[ X ]
  };
};

int main() {
  std::cout << "Foo::Y = " << Foo::Y << std::endl;
}

并提供了一个漂亮可读的语法。

答案 5 :(得分:2)

使用特化而不是实际的数组可能更明智。虽然它变得凌乱......我这样做了一个字符串哈希算法我用模板元编程。部分算法使用了大型查找表,最终看起来像:

template <> struct CrcTab<0x00> { enum { value = 0x00000000 }; };
template <> struct CrcTab<0x01> { enum { value = 0x77073096 }; };
template <> struct CrcTab<0x02> { enum { value = 0xee0e612c }; };

就像这样访问:

CrcTab<index>::value

这样做的好处是你可以在元程序中使用结果,在那里你将无法访问数组。

对于参数广场的值,您甚至不需要专门化。警告:编译器不方便......

template <uint32_t N> struct Square { enum { value = N*N }; };

实际上这凸显了这种方法的缺点,你不能用变量索引......只是一个常数。

答案 6 :(得分:2)

我不确定您是想要看到它还是找到一个能够为您完成的库。我假设前者。

template<int N>
struct SqArr {
    static inline void fill(int arr[]) {
        arr[N-1] = (N-1)*(N-1);
        SqArr<N-1>::fill(arr);
    }
};

template<>
struct SqArr<0> {
    static inline void fill(int arr[]) { }
};

现在让我们尝试使用这个野兽:

#include <iostream>
#include <algorithm>
#include <iterator>

using namespace std;

int main(int argc, char* argv[])
{
    int arr[10];
    SqArr<10>::fill(arr);
    cout << endl;
    copy(arr, arr+10, ostream_iterator<int>(cout, "\t"));
    cout << endl;
    return 0;
}

注意(忏悔):这不是编译时计算。为了激励您可以尝试将第四行从SqArr<N-1>::fill(arr);更改为SqArr<N>::fill(arr);,因此递归是无限的,并且您将看到此编译正常(当编译器实际上应该无限地递归,或者抱怨无限递归)。