【C/C++】いまさらだけど文字列化演算子(#)とトークン連結演算子(##)について理解する

2024-04-16C++,C言語

C/C++のマクロには、文字列化演算子(#)とトークン連結演算子(##)という機能があります。これらを使うと、より強力で便利なマクロを書くことができます。恥ずかしながら、この機能について理解が薄かったので、調べてまとめてみました。

文字列化演算子

文字列化演算子(#)は、引数を取る関数型のマクロで使用できます。仮引数に#をつけることで、引数自体を文字列リテラルに変換します。

使い方は以下のとおりです。

#define STRINGIFY(str) #str

int main()
{
    char a[] = STRINGIFY(test); // char a[] = "test";
    char b[] = STRINGIFY(a); // char b[] = "a";

    return 0;
}

文字列化演算子を使うと、引数自体にダブルクオーテーションを付けて文字列化します。2つ目の例のように、変数を指定しても、変数名が文字列になるだけで変数の中身は参照されません。

これだけだと、なかなか使い道がありませんが、たとえば次のような使い方ができます。

#include <iostream>

#define PRINT_VALUE(value) std::cerr << #value " = " << value << std::endl

int main()
{
    int a = 10;
    char b[] = "This is a pen.";

    PRINT_VALUE(a);
    PRINT_VALUE(b);

    return 0;
}

出力結果は以下のようになります。

a = 10
b = This is a pen.

マクロの展開時に、最初のvalueは文字列化演算子によって変数名が文字列リテラルに変換され、さらに" = "と結合して1個の文字列リテラルとなります。2つ目のvalueには文字列化演算子がついていないので、そのまま変数名として展開されます。

この例ように、デバッグ用の文字列出力などで便利に使うことができます。

トークン連結演算子

トークン連結演算子(##)は、オブジェクト型のマクロと関数型のマクロの両方で使用できます。##の前後のトークンを連結し、1個のトークンに置き換えます。関数型のマクロの場合、仮引数と組み合わせると、引数を含む新たなトークンを作り出すことができます。

使い方は以下のとおりです。

#define CONCATENATE(i) test##i

int main()
{
    int CONCATENATE(1) = 1;  // int test1 = 1;
    int CONCATENATE(2) = 10; // int test2 = 10;

    return 0;
}

引数をtestに連結した新たなトークンを生成します。引数がそのまま連結されるので、この例のように数値を指定することもできます。

これだけだと、正直なところ「コピペでよくないか?」となり、使い道がよくわかりません。しかし、もっと複雑なマクロを定義するときに、トークン連結演算子は役に立ちます。

#define DEFINE_LIST_CONTAINER(struct_name) \
typedef struct struct_name##_list\
{\
    struct struct_name##_list * prev;\
    struct struct_name##_list * next;\
    struct_name * data;\
} struct_name##List;

typedef struct product
{
    char * name;
    unsigned int id;
    unsigned int price;
} Product;

typedef struct person
{
    char * name;
    unsigned int age;
} Person;

DEFINE_LIST_CONTAINER(Product)
DEFINE_LIST_CONTAINER(Person)

マクロの展開時に、引数のProductPersonListと連結され、ProductListPersonListとなり、新たな構造体が定義されます。

このようにテンプレート的に使うことができます。

C,C++

Posted by izadori