LoginSignup
1
0

More than 5 years have passed since last update.

関数の overloading の曖昧さからの template

Last updated at Posted at 2018-03-10

初投稿です。

はじめに

UTF-8 なバイト配列にバイトだけでなく直接 Unicode のコードポイントを追加・削除できたり、iterator をstd::regexとか<algorithm>にそのまま使えたりしたらいいな、と思ったので、std::basic_stringに倣って rope を書いています。ソースは GitHub で保管してます。もうちょっと書いたら push します。

色々今までよく分からなかった C++ のことが、これを書いてるのをきっかけに少し分かるようになった(つもりな)ので、何枚か記事を書いてまとめたいと思います。誰かの役に立てば嬉しいですね。

何をしたかったか

std::basic_string<CharT>のメソッドの例をいくつか挙げます。

basic_string(
    size_type count,
    CharT ch,
    const Allocator& alloc = Allocator()
);

basic_string& operator=(
    CharT ch
);

basic_string& assign(
    size_type count,
    CharT ch
);

当たり前のことですが、std::basic_string<CharT>のメソッドの引数は、CharTがベースです。しかし、バイトもコードポイントも書き込める rope を実装するには、引数をバイトとするメソッドと、引数をコードポイントとするメソッドの両方が欲しいですね。これを実装するには色んな方法があるのですが、そのうち筆者が思いついたものを挙げていきます。

  1. 違う関数名で、引数をバイトとする関数と引数をコードポイントとする関数を書く
  2. template で全部拾って、引数としてバイトを渡しているのか、コードポイントとして渡しているのかを伝える「モード」的な引数を設ける
  3. 同じ関数名でバイトの型とコードポイントの型のオーバーロードを書く
  4. template で全部拾って、ある型に特化したコードポイント版を書き、汎用はバイト版として書く

それぞれの書き方と、良い点と悪い点を考えていきます。

別々の関数を書く

std::basic_string::assignに倣って、rope::assignをこの方法で書いてみるとしましょう。実装はこんな感じになります。

rope& rope::assign(
    size_type count,
    OctetT ch // OctetT はバイトを表す型
) {
    // chをバイトとして何らかの処理を行う
}

rope& rope::assign_cp(
    size_type count,
    CodepointT ch // CodepointT はコードポイントを表す型
) {
    // chをコードポイントとして何らかの処理を行う
}

ユーザからしても、関数名からこの関数がバイトなのかコードポイントなのかが分かるので、筆者はこの方法を使っています。

しかし、コンストラクタのようなメソッドは、関数名を指定できないので、この方法は使えません。別の方法を考えましょう。

template と「モード」引数

rope のコンストラクタを実装すると、こんな感じになります。

enum struct mode {
    octet, cp
};

template <typename GeneralT>
rope(
    size_type count,
    GeneralT ch,
    const mode& m = mode::octet, // デフォルトはバイト
    const Allocator& alloc = Allocator()
) : /* 初期化 */ {
    if (m == mode::octet) {
        // chをバイトとして何らかの処理を行う
    }
    else {
        // chをコードポイントとして何らかの処理を行う
    }
}

template を使っているので、色々な型を受け付けながらも、引数がバイトを表しているのか、コードポイントを表しているのかを指定できるというのは便利ですね。引数がconst T*だったり、std::initializer_list<T>だったりすると、キャストがややこしくなるので、template を使うのは適策だと思います。

しかし、operator をオーバーロードする場合は、引数の量を増やすことはできません。operator は、標準ライブラリの<algorithm>とかに対応するためには不可欠なので、別の方法を考えないといけません。

同じ関数名で overload する

operator=を実装してみましょう。

rope& operator=(OctetT ch) {
    // chをバイトとして何らかの処理を行う
}

rope& operator=(CodepointT ch) {
    // chをコードポイントとして何らかの処理を行う
}

しかし、これだと、OctetTでもCodepointTでもない型の引数を通したい場合、ユーザ側が引数をどちらかにキャストをしないと、曖昧なオーバーロードとしてエラーが出ます。いちいちキャストを書くのは面倒くさいので、何かいい方法はないでしょうか。

template と特化

operator= を実装してみましょう。

template <typename GeneralT>
rope& operator=(GeneralT ch) {
    // chをバイトとして何らかの処理を行う
}

template <>
rope& operator=(CodepointT ch) {
    // chをコードポイントとして何らかの処理を行う
}

引数の型がCodepointTならば、コードポイント版が呼び出されます。そうでない場合は、バイト版が呼び出されます。template の典型的な用例だと思います。

1
0
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0