C++ non type template parameters

模板中的非類型參數

可以在模板中定義非類型參數(non type parameter), 一個非類型參數表示一個值而非一個類型. (C++ Primer 5th P580)

那麼非類型參數可以是哪些呢? 書中的例子是以int爲非類型參數, 那麼string呢?用戶自定義類型呢?

帶着這樣的疑問找到了下面的回答:

https://stackoverflow.com/questions/5547852/string-literals-not-allowed-as-non-type-template-parameters

Question

The following quote is from C++ Templates by Addison Wesley. Could someone please help me understand in plain English/layman’s terms its gist?

Because string literals are objects with internal linkage (two string literals with the same value but in different modules are different objects), you can't use them as template arguments either:

Answer

Your compiler ultimately operates on things called translation units, informally called source files. Within these translation units, you identify different entities: objects, functions, etc. The linkers job is to connect these units together, and part of that process is merging identities.

Identifiers have linkage†: internal linkage means that the entity named in that translation unit is only visible to that translation unit, while external linkage means that the entity is visible to other units.

When an entity is marked static, it is given internal linkage. So given these two translation units:

// a.cpp
static void foo() { /* in a */ } 

// b.cpp
static void foo() { /* in a */ } 

Each of those foo’s refer to an entity (a function in this case) that is only visible to their respective translation units; that is, each translation unit has its own foo.

Here’s the catch, then: string literals are the same type as static const char[..]. That is:

// str.cpp
#include <iostream>

// this code:

void bar()
{
    std::cout << "abc" << std::endl;
}

// is conceptually equivalent to:

static const char[4] __literal0 = {'a', 'b', 'c', 0};

void bar()
{
    std::cout << __literal0 << std::endl;
}

And as you can see, the literal’s value is internal to that translation unit. So if you use “abc” in multiple translation units, for example, they all end up being different entities.‡

Overall, that means this is conceptually meaningless:

template <const char* String>
struct baz {};

typedef baz<"abc"> incoherent;

Because “abc” is different for each translation unit. Each translation unit would be given a different class because each “abc” is a different entity, even though they provided the “same” argument.

On the language level, this is imposed by saying that template non-type parameters can be pointers to entities with external linkage; that is, things that do refer to the same entity across translation units.

So this is fine:

// good.hpp
extern const char* my_string;

// good.cpp
const char* my_string = "any string";

// anything.cpp
typedef baz<my_string> coherent; // okay; all instantiations use the same entity

†Not all identifiers have linkage; some have none, such as function parameters.

‡ An optimizing compiler will store identical literals at the same address, to save space; but that’s a quality of implementation detail, not a guarantee.

翻譯

0. translation unit是什麼

From the C++ Standard:
A source file together with all the headers and source files included via the preprocessing directive #include less any source line skipped by any of the conditional inclusion preprocessing directives is called a translation unit.

一個translation unit就是一個源文件(經過了預處理,#inlcude展開, #if處理).

你通常會在多個translation unit中定義許多entities(實體): object, function… 而編譯器會負責把它們鏈接在一起.此過程包含mergeing identity.

1. 標識符的linkage

標識符(identity)有兩種鏈接屬性:

  • internal linkage 此標識符只在當前translation unit可見
  • external linkage 此標識符對所有translation unit可見

帶有static的實體具備internal linkage.那麼對於下面的兩個translation unit:

// a.cpp
static void foo() { /* in a */ } 

// b.cpp
static void foo() { /* in b */ } 

它們各自有自己的一個函數foo(), 而且函數foo()只能在它所在的源文件使用. 在使用C語言編程時,如果有一個函數不想暴露給其他源文件使用,可以使用static修飾這個函數.

2. 字符串常量

字符串常量是: static const char[..]

// str.cpp
#include <iostream>

// this code:

void bar()
{
    std::cout << "abc" << std::endl;
}

// 等價於:

static const char[4] __literal0 = {'a', 'b', 'c', 0};

void bar()
{
    std::cout << __literal0 << std::endl;
}

也就是說字符串常量是internal linkage, 如果你在其他translation unit中也使用了”abc”, 它們是不同的實體, 並不是同一個.

3. Overall

template <const char* String>
struct baz {};

typedef baz<"abc"> incoherent;

因爲”abc”對於多個的translation unit是不同的. 所以每個translation unit會擁有不同的class, 及時它們使用”同樣的”(僅僅是值相同)參數”abc”.

此外, 不是所有的標識符(identities)都有linkage, 想函數參數就沒有. 優化的編譯器可能會將相同的常量放在同一地址以節省空間.

4. 實驗

編譯上面的代碼, 並沒有通過:

error: ‘"abc"is not a valid template argument for type ‘const char*’ because string literals can never be used in this context
 typedef Foo<"abc"> incoherent;

需要改成:

extern const char my_string[]; 
const char my_string[] = "any string";

>_<

發佈了195 篇原創文章 · 獲贊 64 · 訪問量 50萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章