C ++模板中的一些混淆

时间:2011-08-25 21:10:25

标签: c++ templates

我现在正在阅读本书,C ++模板:完整指南。在一段时间我被卡住了,无法理解术语,这里是段落:

  

一个基本原则是任何模板参数都必须是a   可在编译时确定的数量或价值。变成了   后来清楚,这个要求转化为戏剧性的好处   模板实体的运行时成本。 因为模板参数   最终由编译时值代替,它们本身就可以   用于形成编译时表达式。这被利用了   ArrayInClass模板用于调整成员数组数组的大小。一个大小   数组必须是所谓的常量表达式和模板   参数N符合条件。

     

我们可以进一步推动这个推理:因为模板   参数是编译时实体,它们也可用于创建   有效的模板参数。这是一个例子

template <typename T> 
class Dozen { 
  public: 
    ArrayInClass<T,12> contents; 
};
  

请注意,在此示例中,名称T是模板参数和a   模板参数。因此,有一种机制可以实现   从更简单的模板构建更复杂的模板。当然,   这与允许我们的机制没有根本的区别   组装类型和功能。

我无法理解任何事情。我很欣赏任何有关简单和理解单词的帮助。

修改

Arrayinclass:

template <typename T, int N> 
class ArrayInClass { 
  public: 
    T array[N]; 
};

6 个答案:

答案 0 :(得分:7)

在C ++中有某些表达式需要在编译时知道。例如:

int someArray[30];

30 必须是C ++中的编译时常量。可能是:

int someArray[30 + 3];

没关系,因为编译器具有在编译时计算生成数组所需的所有信息。但是:

void MyFunc(int numItems) {
  int someArray[30 + numItems];
}

这是编译时常量,因为用户可以使用任何整数值调用MyFunc。编译器不知道数组有多大,所以这是一个编译器错误。

(注意:C99允许创建像这样的任意大小的数组.C ++没有。)

因为模板参数是编译时值,所以可以将它传递给需要编译时值的其他地方:

template<int ArrayLen> void MyFunc() {
  int someArray[30 + ArrayLen];
}

这是 legal C ++,因为MyFunc的每次使用都必须指定编译时整数:数组的长度。您不能只是致电MyFunc()致电MyFunc<21>()。由于模板参数必须是编译时可确定的值,因此用户自己无法提供非编译时定义的值。

由于模板参数始终 编译时定义,因此您可以嵌套模板:

template<int ArrayLen> void OtherFunc {
  MyFunc<ArrayLen + 3>();
}

这个新的模板函数调用旧的模板,其数组3比给定的数组大。

答案 1 :(得分:1)

当作者说模板参数最终被编译时常量替换时,他的意思就是这样。例如,在下面众所周知的例子中,生成的代码是一个数字常量(即乘法发生在编译时,而不是运行时:

#include <iostream>

template <int N>
struct Factorial
{
    enum { value = N * Factorial<N-1>::value };
};

template <>
struct Factorial<1>
{
    enum { value = 1 };
};

// example use
int main()
{
    std::cout << Factorial<5>::value << endl;
    return 0;
}

答案 2 :(得分:0)

在您的班级模板Dozen<T>中,模板参数T也被用作参数化成员变量ArrayInClass<T,N>

因此,如果您将Dozen<T>实例化为例如:

Dozen<float> my_dozen;

然后编译器将生成如下代码:

class Dozen<float> { 
  public: 
    ArrayInClass<float,12> contents; 
};

答案 3 :(得分:0)

您以粗体突出显示的部分表示,在定义类Foo<T>时,编译时必须知道编译器T;即它不能是不完整的类型。

对于您实例化T的每个不同类型Foo,编译器会创建一个全新类型(Foo<int>Foo<MyClass>等),这与任何类型无关您可能已经实例化Foo的其他类型,即使它们可能都具有Foo实现所定义的相同行为。

在您发布的示例中,我假设ArrayInClass<T,N>创建了一个T类型的数组N

例如,

template< typename T, size_t N >
class ArrayInClass
{
  T myArr_[N];

public:
  // public interface
};

虽然使用N作为数组长度,但是类中的数组未被动态分配,因为它被声明为静态数组。这是可能的,因为编译器将在实例化ArrayInClass时将您指定的长度替换为模板参数。如果编译器在编译时无法确定N,那就不可能了。

答案 4 :(得分:0)

好的,根据您的评论,这是一个推理的小窍门:

假设您有一个模板:

template <typename T> struct Moo { };

现在我们知道要说Moo<Bar>Bar必须是(在编译时已知的类型)。到目前为止没问题。

现在假设我们要构建一些类:

class MyClass
{
  int         a;
  double      b;
  Moo<double> m;
};

这也没关系,因为Moo<double>是有效类型。再说一遍,没问题。但现在让我们概括MyClass并将其作为模板:

template <typename U>
class MyClass
{
  int    a;
  U      b;
  Moo<U> m;
};

现在再次说MyClass<Zoo>,必须在编译时知道Zoo。因此,依赖类型Moo<U>将替换为Moo<Zoo>中的MyClass<Zoo>,并且因为Zoo已知,此成员类型现在也已知。

你引用的粗体文字只是说这种推理有效并且你得到了有效的东西。

对于类型名称,这并不是非常令人兴奋,因为所有类型名称在编译时都是已知的,但模板参数也可以是非类型名称,即整数值。现在,这些也必须在编译时知道,并且您可以以相同的方式传播它们。

答案 5 :(得分:0)

我认为你很难更清楚地表达引用的段落所说的内容。我认为他们很好地解释了这个问题。

如果您仍然需要帮助来掌握这个想法,请首先尝试理解这三个关键字:模板参数参数。这是我的定义:

模板参数是模板定义的一部分,而模板参数是传递给模板以实例化模板以生成具体类型的内容

模板是可以参数化的功能。在您的示例中,是一个模板:

template <typename T> 
class Dozen
{
...
};

其中 T 十二模板的参数。不久, T 模板参数

也许一个简单的比喻会有所帮助。想象一个模板(这里是十二)作为一个雕塑模型,可以填充液体材料,将设置在铸造内部采用其形状,并最终生产雕塑。现在, T 参数就像液体材料(橡胶,金属,玻璃等),它将赋予雕塑特定的字符。所以,你可以使用相同的演员来制作一系列类似的雕塑。

因此,强制转换中的空心腔代表 T 模板参数,一个占位符,您可以在其中放置模板参数,填充模板。

所以,这大致是元编程中参数化的想法。

转到模板参数和带注释的示例:

  // T states parameter of Dozen template
template <typename T>
class Dozen
{
    // the T is argument used to instantiate concrete type from another template
    ArrayInClass<T,12> contents;
};

在这里,您可能会想到一个函数调用另一个函数和转发参数:

void foo(int a)
{
   bar(a);
}

foo不使用自身,而是将其作为参数传递给bar。 同样,Dozen将自己的模板参数 T 转发为ArrayInClass模板的参数,以实例化此ArrayInClass的具体类型。

最后, T 是编译时表达式。这意味着,它在编译时产生价值。 (表达式是编程语言功能,产生一个值)。表达式的值是类型( T )或数字常量( 12 )。

ArrayInClass<T,12>也是一个编译时表达式,它生成ArrayInClass模板的实例化,生成具体类型。简而言之,compile-type表达式可用于构造另一个编译时表达式 - 产生另一个(复杂)值。

在元编程中,不要将 value 视为数字或字符串。类型也是一个值。