我现在正在阅读本书,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];
};
答案 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 视为数字或字符串。类型也是一个值。