2012-02-16

ユーザー定義リテラルのすべて

C++11にはユーザー定義リテラルというものがある。私はこの機能が嫌いだ。しかし、どうやらgcc 4.7がある程度の実装を終えたらしい。日本一C++に詳しい男を自称する私としては、試さなくてはならない。そこで、この記事を書く。この記事を読めば、今日から君もユーザー定義リテラルをバリバリに使えるようになる。

まず、C++11では、非常に不思議な理由により、ユーザー定義リテラルは、演算子のオーバーロードという形で実装する。はて、リテラルにつくサフィックスは演算子なのだろうか。それはともかく、ユーザー定義リテラルの識別子には、一言注意が必要である。ユーザー定義リテラルの識別子は、必ずアンダースコアからはじめなければならない。

私が注意深く、「識別子」と書いているのには訳がある。ユーザー定義リテラルの識別子は、「名前」ではないからだ。この詳細を理解する必要はない。重要なことは、必ず「識別子」をアンダースコアからはじめなければならないということだ。ちなみに、通常の「名前」では、アンダースコアから始まる「名前」はグローバル名前空間において予約されているので、使ってはいけないし、アンダースコアに大文字から始まる「名前」はあらゆる箇所で予約語なので使ってはいけない。ふたつの連続しているアンダースコアを含む「名前」も予約語である。「識別子」だからこそ、許される所業である。詳しく説明すると、「名前」は「エンティティ」か「ラベル」を意味する「識別子」でなければならないからだ。これはC++プログラマーにとっては一般常識である。

前置きが長くなった。さっそくユーザー定義リテラルを使ってみよう。

#include <iostream>

// 整数の実引数を二倍にして返すユーザー定義リテラル
unsigned long long int operator "" _twice ( unsigned long long int value )
{
    return value * 2 ;
}

int main()
{
    std::cout << 1234_twice << std::endl ;
}

結果として、2468と出力されるはずだ。まあ、とりあえずこれで、ユーザー定義リテラルというものが何かは分かったはずだ。

ユーザー定義リテラルは、

戻り値の型 operator "" _identifer (仮引数リスト)

という形で宣言する。使う際には、

リテラル _identifer

となる。一応、明示的に呼び出すこともできる。もっとも、明示的に呼び出すぐらいなら、普通の人は普通の関数を使うだろうが。

operator "" _identifer() ;

その他は、通常の関数のように使える。中でどのような処理をしてもいいし、戻り値の型は自由だ。

ユーザー定義リテラルには、大きく分けて四種類ある。ユーザー定義整数リテラル、ユーザー定義浮動小数点数リテラル、ユーザー定義文字列リテラル、ユーザー定義文字リテラルである。

ユーザー定義整数リテラル

ユーザー定義整数リテラルを整数型の値として受け取るには、仮引数の型がunsigned long long intでなければならない。もし、単項マイナス演算子を使ったとしたならば、それはユーザー定義リテラルの戻り値に対して適用される。ユーザー定義リテラルに渡される値は、常にunsigned long long intである。

ユーザー定義浮動小数点数リテラル

ユーザー定義浮動小数点数リテラルを浮動小数点数型の値として受け取るには、仮引数の型がlong doubleでなければならない。

long double operator "" _udl( long double value )
{ 
    return value ;
}

int main()
{
    3.14_udl ;
}

浮動小数点数の場合も、負数は受け取れない。単項マイナス演算子は、ユーザー定義リテラルの評価の結果に適用される。

ユーザー定義整数リテラルとユーザー定義浮動小数点数リテラルをその他の方法で受け取る方法

この整数リテラルと浮動小数点数リテラルのユーザー定義リテラルは、別の方法で受け取ることもできる。文字列と、テンプレート実引数である。

文字列として受け取る場合、char const *を使う。

// 単にstd::string型として返す
std::string operator "" _to_string( char const * s )
{
    return std::string( s ) ; 
}

int main()
{
    // operator "" _to_string("1234")
    std::string s = 1234_to_string ;
    // operator "" _to_string("1.234")
    s = 1.234_to_string ;
}

何のひねりもない単純なコードだが、みれば分かるだろう。整数リテラルを、あたかも""で囲んで通常文字列リテラルにして、そのまま実引数に渡したかのように、ユーザー定義リテラルのオーバーロード関数に渡される。

テンプレート実引数として受け取る場合は、仮引数を取らない。Variadic Templateを使って実装する。

// 単に結合してstd::string型として返す
template < char ... Chars >
std::string operator "" _to_string( )
{
    std::string buf ;
    for ( auto c : std::initializer_list<char>{ Chars... } )
    {
        buf.push_back( c ) ;
    }
    return buf ; 
}

int main()
{
    // operator "" _to_string<'1', '2', '3', '4'>()
    std::string s = 1234_to_string ;
    // operator "" _to_string<'1', '.', '2', '3', '4'>()
    s = 1.234_to_string ;
}

これまた、なんのひねりもない単純なコードだが、みれば分かるだろう。整数リテラルと浮動小数点数リテラルの、各文字がそれぞれ、<'1', '2', '3', '4'>という形で、テンプレート実引数として渡される。

すでに説明したので、いまさら言うまでもないと思うが、この文字列とテンプレート実引数で受け取る機能は、整数リテラルと浮動小数点数リテラル限定である。この次に出てくる文字列リテラルと文字リテラルには使えない。もっとも、聡明な読者には余計なおせっかいであろう。失礼失礼。

ユーザー定義文字列リテラル。

C++11では、5種類の文字列がある。このうち、通常の文字列リテラルとUTF-8文字列リテラルは、型を共有しているので、おなじ仮引数で受け取る。

// 通常の文字列リテラルとUTF-8文字列リテラル
void operator "" _udl( char const * str, std::size_t size ) { }
// ワイド文字列リテラル
void operator "" _udl( wchar_t const * str, std::size_t size ) { }
// char16_t文字列リテラル
void operator "" _udl( char16_t const * str, std::size_t size ) { }
// char32_t文字列リテラル
void operator "" _udl( char32_t const * str, std::size_t size ) { }

int main()
{
    // operator "" _udl("test", 4)
    "test"_udl ;
    // operator "" _udl(u8"test", 4)
    u8"test"_udl ;
    // operator "" _udl(L"test", 4)
    L"test"_udl ;
    // operator "" _udl(u"test", 4)
    u"test"_udl ;
    // operator "" _udl(U"test", 4)
    U"test"_udl ;
}

戻り値の型がvoidという、何の意味もないコードだが、あくまで例示のためである。第二引数は、NULL文字を除く文字数を格納している。第一引数はちゃんとNULL終端されているので安心して欲しい。

また老婆心で忠告するが、char *という仮引数リストは、ユーザー定義文字列リテラルのためにあるのではない。整数リテラルと浮動小数点数リテラルを文字列として受ける場合の仮引数リストである。文字列リテラルには使えない。

とても大事な事なのでもう一度ぐらい忠告しても許されると思うから言っておくが、Variadic Templateは、整数リテラルと浮動小数点数リテラルを受け取る場合の宣言である。文字列リテラルには使えない。

ユーザー定義文字リテラル

文字列リテラルの場合とほとんど変わりない。

// 通常の文字リテラルとUTF-8文字リテラル
void operator "" _udl( char c ) { }
// ワイド文字リテラル
void operator "" _udl( wchar_t c ) { }
// char16_t文字リテラル
void operator "" _udl( char16_t c ) { }
// char32_t文字リテラル
void operator "" _udl( char32_t c ) { }

int main()
{
    // operator "" _udl("a")
    "a"_udl ;
    // operator "" _udl(u8"a")
    u8"a"_udl ;
    // operator "" _udl(L"a")
    L"a"_udl ;
    // operator "" _udl(u"a")
    u"a"_udl ;
    // operator "" _udl(U"a")
    U"a"_udl ;
}

ユーザー定義リテラルとは、これで全てである。ユーザー定義リテラルは、戻り値の型や識別子を除いては、上記のいずれかの宣言でなければならない。これ以外の仮引数リストやテンプレートは使えない。例えば、以下のようなコードはエラーとなる。宣言すらできない。

// エラー、この仮引数リストは不正
void operator "" _udl ( int ) ;

// エラー、通常の関数でなければならない
template < typename T >
void operator "" _udl ( T ) ;

// これもエラー、Variadic Templatesでなければならない。
template < char c1, char c2, char c3, char c4  >
void operator "" _udl () ;

これでユーザー定義リテラルのすべてを解説した。ところで、私はユーザー定義リテラルが嫌いである。

No comments:

Post a Comment

You can use some HTML elements, such as <b>, <i>, <a>, also, some characters need to be entity referenced such as <, > and & Your comment may need to be confirmed by blog author. Your comment will be published under GFDL 1.3 or later license with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.