質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.61%

親クラスのメンバ変数(参照)を初期化できない

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 2,164

score 170

質問内容

下記コードはメンバに

  • 配列or動的配列への参照
  • 配列のサイズを記すメモリへの参照
  • 内部カウンタ変数(参照ではない)

を所持し、引数付きコンストラクタでそれら変数を初期化する
イテレータを作成しようとしたコードです。

登場するクラスは

クラス名 備考
BaseIterator 主にカウンタ変数にまつわる処理が記された基底クラス
Iterator 主に要素書き換えられるようにを参照で返す処理の記された派生クラス
ConstIterator 主に要素書き換えられないようにコピーで返す処理の記された派生クラス

より構成されております。

しかし派生クラスの引数付きコンストラクタでコンパイルエラーが発生します。

エラー文(下記記載)を読む限りだと
派生クラスのコンストラクタでメンバ変数element_(参照)を操作できないと言っているようですが
対処法がわかりません。

どのように書けばメンバ変数element_をコンストラクタの引数で初期化できるのでしょうか

どうかご回答よろしくお願い致します。

#include <iostream>
#include <memory>
#include <cassert>

///////////////////////////////////////////
//
//  Iterator基底クラス(抽象クラスではない)
//
///////////////////////////////////////////
template<typename Type, typename SizeType>
class BaseIterator{
    protected:
        Type& element_;// 配列の参照
        SizeType& size_;// サイズの参照
        SizeType counter_ = SizeType(0);// カウンタ

    public:
        BaseIterator()noexcept = delete;
        virtual ~BaseIterator() = default;

        auto operator++(int){//  後置++operator
            this->counter_++;
            assert(this->counter_ <= this->size_);
            return *this;
        }

        decltype(auto) operator++(){//  前置++operator
            ++this->counter_;
            assert(this->counter_ <= this->size_);
            return *this;
        }

        auto operator--(int){//  後置--operator
            this->counter_--;
            return *this;
        }

        decltype(auto) operator--(){//  前置--operator
            --this->counter_;
            return *this;
        }

        decltype(auto) operator=(const SizeType index)noexcept{// カウンターに数値歳入
            assert(index <= this->size_);
            this->counter_ = index;
            return *this;
        }

        //
        //  ここから比較関連の処理
        //
        auto operator==(const BaseIterator<Type, SizeType>& itr)const noexcept{return this->counter_ == itr.counter_;}
        auto operator!=(const BaseIterator<Type, SizeType>& itr)const noexcept{return this->counter_ != itr.counter_;}
        auto operator>(const BaseIterator<Type, SizeType>& itr)const noexcept{return this->counter_ > itr.counter_;}
        auto operator<(const BaseIterator<Type, SizeType>& itr)const noexcept{return this->counter_ < itr.counter_;}
        auto operator>=(const BaseIterator<Type, SizeType>& itr)const noexcept{return this->counter_ >= itr.counter_;}
        auto operator<=(const BaseIterator<Type, SizeType>& itr)const noexcept{return this->counter_ <= itr.counter_;}

};

///////////////////////////////
//
//  Iteratorクラス(参照を返す)
//
///////////////////////////////
template<typename  Type, typename  SizeType>
struct Iterator final:public BaseIterator<Type, SizeType>{
    //↓ここでコンパイルエラー発生
    Iterator(Type& element, SizeType& size)noexcept:BaseIterator<Type, SizeType>::element_(element),BaseIterator<Type, SizeType>::size_(size){}

    Iterator()noexcept = delete;
    ~Iterator() = default;

    //  カウンタの指す要素の参照を返す
    decltype(auto) operator*(){return this->element_[this->counter_];}

    //  指定した要素の参照を返す
    decltype(auto) operator[](const SizeType size){
        assert(size <= this->size_);
        return this->element_[size];
    }
};

///////////////////////////////////////////////////
//
//  ConstIteratorクラス(コピーされた別メモリを返す)
//
///////////////////////////////////////////////////
template<typename  Type, typename  SizeType>
struct ConstIterator final:public BaseIterator<Type, SizeType>{
    //↓ここでコンパイルエラー発生
    ConstIterator(Type& element, SizeType& size)noexcept:BaseIterator<Type, SizeType>::element_(element),BaseIterator<Type, SizeType>::size_(size){}

    ConstIterator()noexcept = delete;
    ~ConstIterator() = default;

    //  カウンタの指す要素のコピーを返す
    auto operator*()const {return this->element_[this->counter_];}

    //  指定した要素のコピーを返す
    auto operator[](const SizeType size)const {
        assert(size <= this->size_);
        return this->element_[size];
    }
};

///////////////
//
//  main関数
//
//////////////
int main(){

    auto size = size_t(5);
    auto mem = std::make_unique<int[]>(size);

    auto itr = ConstIterator(mem, size);

    return 0;
}

エラー文

[コードのパス]/main.cpp:90:88: error: typename specifier refers to non-type member 'element_' in 'BaseIterator<std::unique_ptr<int [], std::default_delete<int []> >, unsigned long>'
    ConstIterator(Type& element, SizeType& size)noexcept:BaseIterator<Type, SizeType>::element_(element),BaseIterator<Type, SizeType>::size_(size){}// ここでコンパイルエラー発生
                                                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~
[コードのパス]/main.cpp:114:16: note: in instantiation of member function 'ConstIterator<std::unique_ptr<int [], std::default_delete<int []> >, unsigned long>::ConstIterator' requested here
    auto itr = ConstIterator(mem, size);
               ^
[コードのパス]/main.cpp:14:15: note: referenced member 'element_' is declared here
        Type& element_;// 配列の参照
              ^
[コードのパス]/main.cpp:90:88: error: 'element_' is a protected member of 'BaseIterator<std::unique_ptr<int [], std::default_delete<int []> >, unsigned long>'
    ConstIterator(Type& element, SizeType& size)noexcept:BaseIterator<Type, SizeType>::element_(element),BaseIterator<Type, SizeType>::size_(size){}// ここでコンパイルエラー発生
                                                                                       ^
[コードのパス]/main.cpp:14:15: note: must name member using the type of the current context 'ConstIterator<std::unique_ptr<int [], std::default_delete<int []> >, unsigned long>'
        Type& element_;// 配列の参照
              ^
[コードのパス]/main.cpp:90:136: error: typename specifier refers to non-type member 'size_' in 'BaseIterator<std::unique_ptr<int [], std::default_delete<int []> >, unsigned long>'
    ConstIterator(Type& element, SizeType& size)noexcept:BaseIterator<Type, SizeType>::element_(element),BaseIterator<Type, SizeType>::size_(size){}// ここでコンパイルエラー発生
                                                                                                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~
[コードのパス]/main.cpp:15:19: note: referenced member 'size_' is declared here
        SizeType& size_;// サイズの参照
                  ^
[コードのパス]/main.cpp:90:136: error: 'size_' is a protected member of 'BaseIterator<std::unique_ptr<int [], std::default_delete<int []> >, unsigned long>'
    ConstIterator(Type& element, SizeType& size)noexcept:BaseIterator<Type, SizeType>::element_(element),BaseIterator<Type, SizeType>::size_(size){}// ここでコンパイルエラー発生
                                                                                                                                       ^
[コードのパス]/main.cpp:15:19: note: must name member using the type of the current context 'ConstIterator<std::unique_ptr<int [], std::default_delete<int []> >, unsigned long>'
        SizeType& size_;// サイズの参照

開発環境の備考

上記コードはすべてc++20でコンパイルするものとする。

ツールの種類 ツールの名前 バージョン
コンパイラ clang++ 10.0.0
OS Linux Mint 20.0
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

0

そもそも移譲コンストラクタは、同じクラス内の別のコンストラクタを呼び出す機能ですから、それは移譲コンストラクタではありません。
移譲コンストラクタ - cpprefjp C++日本語リファレンス

で、そもそも基底クラスにprotectedなものがある設計自体とても汚いと感じるわけですが、
そのprotectedいらなくない? - Qiita
まあそこは元のコードを尊重するということで放置して、やるならこうじゃないですかね?

#include <iostream>
#include <memory>
#include <cassert>

///////////////////////////////////////////
//
//  Iterator基底クラス(抽象クラスではない)
//
///////////////////////////////////////////
template<typename Type, typename SizeType>
class BaseIterator{
    protected:
        Type& element_;// 配列の参照
        SizeType& size_;// サイズの参照
        SizeType counter_ = SizeType(0);// カウンタ
        BaseIterator(Type& element, SizeType& size) noexcept : element_(element), size_(size){}
    public:
        BaseIterator() noexcept = delete;
        virtual ~BaseIterator() = default;

        auto operator++(int){//  後置++operator
            this->counter_++;
            assert(this->counter_ <= this->size_);
            return *this;
        }

        decltype(auto) operator++(){//  前置++operator
            ++this->counter_;
            assert(this->counter_ <= this->size_);
            return *this;
        }

        auto operator--(int){//  後置--operator
            this->counter_--;
            return *this;
        }

        decltype(auto) operator--(){//  前置--operator
            --this->counter_;
            return *this;
        }

        decltype(auto) operator=(const SizeType index)noexcept{// カウンターに数値歳入
            assert(index <= this->size_);
            this->counter_ = index;
            return *this;
        }

        //
        //  ここから比較関連の処理
        //
        auto operator==(const BaseIterator<Type, SizeType>& itr)const noexcept{return this->counter_ == itr.counter_;}
        auto operator!=(const BaseIterator<Type, SizeType>& itr)const noexcept{return this->counter_ != itr.counter_;}
        auto operator>(const BaseIterator<Type, SizeType>& itr)const noexcept{return this->counter_ > itr.counter_;}
        auto operator<(const BaseIterator<Type, SizeType>& itr)const noexcept{return this->counter_ < itr.counter_;}
        auto operator>=(const BaseIterator<Type, SizeType>& itr)const noexcept{return this->counter_ >= itr.counter_;}
        auto operator<=(const BaseIterator<Type, SizeType>& itr)const noexcept{return this->counter_ <= itr.counter_;}

};

///////////////////////////////
//
//  Iteratorクラス(参照を返す)
//
///////////////////////////////
template<typename  Type, typename  SizeType>
struct Iterator final:public BaseIterator<Type, SizeType>{
    //↓ここでコンパイルエラー発生
    Iterator(Type& element, SizeType& size) noexcept : BaseIterator<Type, SizeType>(element, size) {}

    Iterator()noexcept = delete;
    ~Iterator() = default;

    //  カウンタの指す要素の参照を返す
    decltype(auto) operator*(){return this->element_[this->counter_];}

    //  指定した要素の参照を返す
    decltype(auto) operator[](const SizeType size){
        assert(size <= this->size_);
        return this->element_[size];
    }
};

///////////////////////////////////////////////////
//
//  ConstIteratorクラス(コピーされた別メモリを返す)
//
///////////////////////////////////////////////////
template<typename  Type, typename  SizeType>
struct ConstIterator final:public BaseIterator<Type, SizeType>{
    //↓ここでコンパイルエラー発生
    ConstIterator(Type& element, SizeType& size) noexcept : BaseIterator<Type, SizeType>(element, size) {}

    ConstIterator()noexcept = delete;
    ~ConstIterator() = default;

    //  カウンタの指す要素のコピーを返す
    auto operator*()const {return this->element_[this->counter_];}

    //  指定した要素のコピーを返す
    auto operator[](const SizeType size)const {
        assert(size <= this->size_);
        return this->element_[size];
    }
};

///////////////
//
//  main関数
//
//////////////
int main(){

    auto size = size_t(5);
    auto mem = std::make_unique<int[]>(size);

    auto itr = ConstIterator(mem, size);

    return 0;
}

https://wandbox.org/permlink/IJJvIQtlluedZIRj

ちなみにC++20を使えるなら三方比較演算子を使うと楽になると思います。


追記

なぜ派生クラスのコンストラクタのメンバー初期化リスト(mem-initializer-list)で基底クラスのメンバー変数を初期化できないか

mem-initializer-listとは

まずはC++規格書の定義を見てみましょう。

class.base.init/1
  In the definition of a constructor for a class, initializers for direct and virtual base class subobjects and non-static data members can be specified by a ctor-initializer, which has the form

ctor-initializer:
    : mem-initializer-list

mem-initializer-list:
    mem-initializer ...opt
    mem-initializer-list , mem-initializer ...opt

mem-initializer:
    mem-initializer-id ( expression-listopt )
    mem-initializer-id braced-init-list

mem-initializer-id:
    class-or-decltype
    identifier

英文は読まずともまあ十分わかりやすいかと思いますが、例を見ながら確認しましょう。

struct foo {
    int bar;
    int hoge;
    foo() : bar(3), hoge(7) {}
};

上記で言うbar(3), hoge(7)mem-initializer-listです。またbar(3)hoge(7)mem-initializerです。そしてbarhogemem-initializer-idです。

有効なmem-initializer-idとは

またしても規格書を見ていきます。

class.base.init/2
  In a mem-initializer-id an initial unqualified identifier is looked up in the scope of the constructor's class and, if not found in that scope, it is looked up in the scope containing the constructor's definition.
[ Note: If the constructor's class contains a member with the same name as a direct or virtual base class of the class, a mem-initializer-id naming the member or base class and composed of a single identifier refers to the class member.
mem-initializer-id for the hidden base class may be specified using a qualified name.
— end note
]
Unless the mem-initializer-id names the constructor's class, a non-static data member of the constructor's class, or a direct or virtual base of that class, the mem-initializer is ill-formed.

とくに太字で強調したUnless以降が大事なのでまとめてみます。これによれば、有効なmem-initializer-idとは次の3つです。

  • コンストラクタを書いているクラス自身の名前: 移譲コンストラクタのこと
  • コンストラクタを書いているクラス自身のstatic指定されていないメンバー変数の名前
  • 基底クラス(仮想基底クラスを含む)の名前

これ以外を指定した場合、ill-formedである、と書かれています。

これはどういうことかと言うと、メンバー初期化リストの時点では基底クラスは初期化されていないためアクセスできないということだと考えられます。

以上の内容はbase class protected member init directでググって一番上に出てきた
inheritance - Initialize parent's protected members with initialization list (C++) - Stack Overflow
を元に書かれています。

結論

基底クラスのメンバー変数は有効なmem-initializer-idではないのでill-formed

protectedメンバーを使うことは妥当か

しかしそれでは各Iteratorにカウンタ操作を指定するoperatorを記載せねばならず
コードのサイズが増えてしまうように感じるのですが
それでも依存関係の簡略化を優先したほうが良いのでしょうか。

C++のイテレータを作るのがとっても面倒なのはものすごくよくわかります。

実際にSTLの実装を覗いてみることにします。

msvcの実装

https://github.com/microsoft/STL/blob/master/stl/inc/vector
https://github.com/microsoft/STL/blob/master/stl/inc/xmemory

これは_Vector_iterator_Vector_const_iterator_Iterator_baseという継承になっていて、operator類はそれぞれ定義しているのがわかります。

libstdc++の実装

https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/stl_vector.h
https://github.com/gcc-mirror/gcc/blob/master/libstdc++-v3/include/bits/stl_iterator.h

イテレータに関しては極めてシンプルで、iteratorとconst_iteratorは同じ__normal_iteratorクラスでできています。protectedメンバーがありますが、std::vectorから使う文についてはprotectedである必要を見いだせないので多分別の用途で使ってるのでしょう。

結論

言い出しておいてなんですが正直わからんです。自分はC++のイテレータに関してはKISSの法則を投げ捨てたほうがうまくいく気がします。比較系は先述の通り三方比較演算子のおかげで自動定義がある程度使えますが、その他は愚直に書くのがなんだかんだいいのかなと思っています。依存名か非依存名かでうっかり引っかかったりする心配もないですし。

本件とは別の問題点からになりますが、これまでイテレータを作るときに継承するべきとされていたstd::iteratorはC++17でdeprecatedになりました。まあそんなこんなであんまり継承したくない心情です。
P0174R2 Deprecating Vestigial Library Parts in C++17


追記2

私の勝手な憶測なのですが
もしかして

規格1:
その規格で必要になる規格1
その規格で必要になる規格2
  といった書き方がされているのでしょうか。

違います。構文を定義する文法です。C++に限らず広く用いられる記法です。
まず定義したい構文の名称を書き、それを構成する構文や記号などの並びをその下に字下げして書きます。
例えば識別子(identifier)の定義は
https://timsong-cpp.github.io/cppwp/n4861/lex.name#nt:identifier
にあるように表されます。

identifier:
    identifier-nondigit
    identifier identifier-nondigit
    identifier digit

これはつまり、identifierという構文は、その下の各行のいずれかのことである、という定義です。じゃあidentifier-nondigitってなに?と思って調べるとこれは

identifier-nondigit:
    nondigit
    universal-character-name

のように定義されています。じゃあnondigitは?というと

nondigit: one of
    a b c d e f g h i j k l m
    n o p q r s t u v w x y z
    A B C D E F G H I J K L M
    N O P Q R S T U V W X Y Z _

と定義されています。

ちょっと戻ってもう一度これを見てください。

identifier:
    identifier-nondigit
    identifier identifier-nondigit
    identifier digit

identifier identifier-nondigitってどういうこと?と思うかもしれません。なんでidentifierの定義にidentifierが出てくるねん!ってなりますよね。これはつまりは再帰的定義をしているわけです。

例えばanondigitであり、identifier-nondigitでもあり、identifierでもあります。

ではabはどうでしょうか?まずbは同様にしてidentifier-nondigitであると言えます。aidentifierであることはすでに述べたとおりです。したがってidentifier identifier-nondigitの定義に当てはまり、abidentifierだと言えます(定義文はスペースで区切られていますが、このスペースは定義文に含まれる構文用語を識別しやすくするためのものであってスペースを入れろという意味ではありません。つまりa bではなくabということです。定義にスペースを入れてほしい場合は明示的にspaceみたいな構文用語を記述するはずです)。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/08/12 21:01

    ついに解答欄の1万文字制限に達したので残りをここに書きます。


    他の言語だと、例えばECMAScript(JavaScript)の規格書で、コメントを表す構文の定義は
    [https://www.ecma-international.org/ecma-262/#sec-comments](https://www.ecma-international.org/ecma-262/#sec-comments)
    にあるように表されます(注意: ページのロードに極めて時間がかかります)

    別の種類の構文定義記法としてはBNFがあります。というかこっちのほうが有名かな。
    [バッカス・ナウア記法 - Wikipedia](https://ja.wikipedia.org/wiki/%E3%83%90%E3%83%83%E3%82%AB%E3%82%B9%E3%83%BB%E3%83%8A%E3%82%A6%E3%82%A2%E8%A8%98%E6%B3%95)

    キャンセル

  • 2020/08/12 21:02

    そろそろ本題からそれてきているのに加えて、解答欄に余白がもうないので、これ以上は別途質問を立ててください。こちらからはフォローしておきましたのでそちらから私をフォローすれば回答リクエスト機能で私に通知が来るようにできると思います。あるいは新しい質問のURLをここに貼っても通知が来ます。

    キャンセル

  • 2020/08/13 12:37 編集

    yumetodoさん
    御返事誠にありがとうございます。

    御回答拝見いたしました。
    しかしとても高度な内容に見えるので
    時間をかけて読んでいくことにします。

    私の方でも回答依頼ができるよう
    フォローさせて頂きますので
    ご質問したときは
    どうかよろしく御願い致します。

    あと元コードの改良版
    を考えていたのでwandboxのリンク貼っておきます。
    https://wandbox.org/permlink/xQRYln8PX9eN8Rg1

    いろいろ考えてみましたがprotectedは消せなかったです
    しかし三方比較演算子の便利さは実感できました。
    これから積極的に使っていこうと思います。

    今回とても親身に取り合っていただけ
    大変光栄に感じております。

    ご回答頂き大変有難うございました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.61%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る