012: constructor with iterator

イテレータを伴うコンストラクタを定義します。7つあるコンストラクタの6つ目の定義になります。

引数に SFINAE を使ったテクニックと、イテレータ間の距離を取得する関数も用意します。

template <typename T>
class myvector
{
...

    /**
     * @brief      Constructs the container with the contents of the range [first, last).
     * @param[in]  first: First of the range to copy the elements from.
     * @param[in]  last: Last of the range to copy the elements from.
     * @throw      std::length_error: If distance between first and last is greater than max_size().
     * @throw      std::bad_alloc: If malloc() fails to allocate storage.
     * @tparam     InputIt: Iterator to the elements.
     */
    template <class InputIt>
    myvector(InputIt first,
             typename std::enable_if<not std::is_integral<InputIt>::value, InputIt>::type last)
    {
        // Switcher for mydistance(). Whether InputIt is random access iterator or input iterator.
        using iterator_type = typename std::iterator_traits<InputIt>::iterator_category;

        size_type count = mydistance(first, last, iterator_type());
        if (count) {
            heap_ = mymalloc(length_check(count));
            size_ = count;
            capacity_ = count;

            pointer p = heap_;
            for (InputIt i = first; i != last; ++i) {
                new(p++) value_type(*i);
            }
        }
    }
...

private:
    /**
     * @brief      Counts distance from first to last.
     *             The return type is size_type (not difference_type).
     * @param[in]  first: First of the range.
     * @param[in]  last: Last of the range.
     * @param[in]  std::random_access_iterator_tag: Function switcher according to iterator type.
     * @return     Distance from first to last in size_type.
     * @tparam     RandomIt: Random access iterator of the range.
     */
    template <class RandomIt>
    size_type mydistance(RandomIt first, RandomIt last, std::random_access_iterator_tag) const
    {
        if ((first <= last) && (last - first >= 0)) { // confirm never overflows
            return last - first;
        }
        else {
            return mydistance(first, last, std::input_iterator_tag());
        }
    }

    /**
     * @brief      Counts distance from first to last.
     *             The return type is size_type (not difference_type).
     * @param[in]  first: First of the range.
     * @param[in]  last: Last of the range.
     * @param[in]  std::input_iterator_tag: Function switcher according to iterator type.
     * @return     Distance from first to last in size_type.
     * @tparam     InputIt: Input iterator of the range.
     */
    template <class InputIt>
    size_type mydistance(InputIt first, InputIt last, std::input_iterator_tag) const
    {
        size_type count = 0;
        for (InputIt i = first; i != last; ++i) {
            count++;
        }

        return count;
    }
};
イテレータを伴うコンストラクタの動作フローは、
①イテレータ間の距離を取得
②距離分だけメモリを確保
③イテレータを順に placement new で代入
となります。このフロー自体は特殊なことはしていません。

メインのフローとは別のところで、テンプレートテクニックが使われています。

1つ目は、SFINAE です。今回定義したコンストラクタの2つ目の引数の型に、複雑なテンプレートが書いてあるのがそれです。
myvectorクラスには、同じく引数が2つのコンストラクタ {サイズと初期値指定} があります。仮に myvector<int> v(1, 10); と変数を定義した時、サイズが 1 で要素の値が 10 の myvector ができることが予想されます。しかし、SFINAE を使っていない場合、今回定義したコンストラクタが呼ばれてしまい、コンパイルエラーとなってしまいます。
この問題を回避するのが、2つ目の引数の型にある複雑なテンプレートです。引数の型が整数型でなければこのコンストラクタのコンパイルに成功せず、コンパイラは別の関数候補を探しに行くようになります(そして、{サイズと初期値指定} のコンストラクタを見つける)。

2つ目は、mydistance()関数です。これは、標準関数の std::distance() と同等の動きをするのですが、戻り値の型が size_type である点が異なります。
また、引数の型のイテレータ種別によって最適な計算方法が異なるので、イテレータ種別によって関数を振り分けるようになっています。


全ソースコード: https://github.com/suomesta/myvector/tree/master/012

0 件のコメント:

コメントを投稿