Edited at

C++でC#のnameof的なことをする


(An English version is available here.)


はじめに

C#にはnameof演算子という、変数、型、メンバーの文字列を取得する演算子があります。

nameof演算子のページで紹介されている例としては、ログを取る際にメソッドの名前を渡す例があります。その際に文字列リテラルで渡すと、静的解析の際にメソッドの名前として扱われないので、リネームの際に自動で追従しません。1

ここで、nameofを使うと、次のようになります。

void f(int i) {  

Log(nameof(f), "method entry");
}

このように書くと、nameof(f)のfも識別子として静的解析の対象になるので、IDEのリネーム機能を使う際に、リネーム対象として取り扱ってくれます。

この記事では、C++でも同様の機能がないのか、ということについて述べます。


__func__

C++では関数のボディで関数名を取得できる識別子として、__func__があります。

例えば、次のように使います。

void f()

{
std::cout << "This function is " << __func__ << std::endl;
}

関数fを呼び出すと、This function is fが出力されます。

__func__は関数のボディ内でその関数名を取り出せますが、C#のnameofはどの関数でも、どのクラスでも、どの変数でも、識別子さえあれば使えます。C++でももっと汎用的なものがあればよいのですが・・・。


マクロを定義する

C++の関数ライクなマクロでは、仮引数に#を前置すると、実引数に渡された値の文字列リテラルが取り出せます。例えば、次のコードは、5+5 = 10が出力されます。

#define PRINT(int) printf(#int " = %d\n",int)

PRINT(5+5);

よって、次のNAMEOFマクロを定義すれば、C#のnameofに近いことができます。

#define NAMEOF(name) #name

使い方は次のとおりです。

struct MyStruct

{
int myMemberVariable = 0;
};

class MyClass
{
public:
int myMemberFunction() { return 0; }
};

int myGlobalVariable = 0;

void myFunction()
{
}

int main()
{
std::cout << NAMEOF(MyStruct) << std::endl;
std::cout << NAMEOF(MyStruct{}.myMemberVariable) << std::endl;
std::cout << NAMEOF(MyClass{}.myMemberFunction) << std::endl;
std::cout << NAMEOF(myGlobalVariable) << std::endl;
std::cout << NAMEOF(myFunction) << std::endl;
}

次の出力が得られます。

MyStruct

MyStruct{}.myMemberVariable
MyClass{}.myMemberFunction
myGlobalVariable
myFunction

ここで、myFunctionに対してリネームをかけると、NAMEOFの中のmyFunctionに対してもリネームをしてくれました。(ReSharperのリネーム機能で確認しています。)

・・・ですが、「C#のnameofに近いこと」と言ったのは訳があります。

ここで、定義されていないmyNonexistentWhateverという識別子を渡したとします。

    std::cout << NAMEOF(myNonexistentWhatever) << std::endl;

これは、コンパイルが通り、myNonexistentWhateverと出力されます。これは、マクロの展開はテキストの編集であり、myNonexistentWhatever が何であるのかということは考慮されないためです。マクロが展開されると"myNonexistentWhatever"という文字列リテラルになるので、上の例では、C++としてコンパイル可能なコードになります。

すなわち、最初にNAMEOFに識別子を渡すコードを書く際に、typoをしていないかどうか確認する必要があります。

と、欠点を述べましたが、少なくとも文字列リテラルをそのまま書くよりはマシだと思います。

このNAMEOFマクロを更にC#のnameof寄りに発展させたものとして、bravikov/nameofというライブラリがあります。「C#のnameof寄り」というのは、例えば、enum Foo {Bar}に対してnameof(Foo::Bar)と書くと、Fooを省いて"Bar"と評価されるようになっていることです。


おわりに

この話題は、ググってもあまり出てこず、NAMEOFマクロについては、StackOverflowのコメントをもとに、実験・考察したものです。ですので、他にも良い方法がないか気になります。ご教授いただけると助かります。


参考

https://stackoverflow.com/questions/14351971/what-does-x-inside-a-c-macro-mean

https://www.cprogramming.com/tutorial/cpreprocessor.html

https://wandbox.org/permlink/w2tGPxN8UHknzNuT





  1. ReSharperのリネーム機能では文字列リテラルも検索対象にできますが、結局、開発者が目で見てリネーム対象かどうか決める必要あります。