#43 テンプレート化された基底クラスの名前へのアクセス方法

Effective C++ 43項の内容です.

テンプレート化された基底クラスの名前は,派生クラスからは見えない.

ということで,サンプルコードを見ていきます.まず,下記のような基底クラスと派生クラスがあったとします.

class ElementA {
public:
  void sayType() { std::cout << "ElementA" << std::endl; }

};

class ElementB {
public:

};

template<typename ElemType>
class BaseStorage {
public:
  void sayType() { 
    ElemType e;
    e.sayType();
  }
};

/** これはNG
template<typename ElemType>
class InheritedStorageANG : public BaseStorage <ElemType> {
public:
  void clarifyType() { sayType(); }
};
**/

今までのルールで言うと,InheritedStorageANGの sayType() 呼び出しは,派生クラス名前が隠蔽されていないのででコンパイルが通るはずです.が,今回のケースではコンパイルが通りません.これがまさに今回の "テンプレート化された基底クラスの名前は,派生クラスからは見えない." という内容になります.なぜ C++ のルールがこういうふうになっているかというと,sayType() を持たない BaseStorage を定義することが可能で,テンプレートパラメータ ElemType の型がはっきりするまで,コンパイラには sayType() メソッドを持つかどうかわからないからです.(だから,派生クラスの内部に該当メソッドがあるかどうか見に行きません.)

どういうことかというと,BaseStorage はテンプレート特化させて下記のように複数の定義を持つことが可能です.

class ElementA {
public:
  void sayType() { std::cout << "ElementA" << std::endl; }

};

class ElementB {
public:

};

template<typename ElemType>
class BaseStorage {
public:
  void sayType() { 
    ElemType e;
    e.sayType();
  }
};

// テンプレート特化を使うと,sayTypeを持たないBaseStorageの定義が可能になる.
template<>
class BaseStorage<ElementB> {
};

上記のサンプルでわかることは,テンプレートパラメータの値によって,BaseStorageの中身が変わるということです.つまり,テンプレートパラメータの値が確定していない時点で,派生クラスに sayType() があるかどうかわからないわけです.ではでは,どのように基底クラスのメソッドやメンバを呼びだせばいいかという話ですが,呼び出す方法は3つあります.

1.基底クラスの関数呼び出しの前に this-> をつける.
template<typename ElemType>
class InheritedStorageA1 : public BaseStorage <ElemType> {
public:
  void clarifyType() { this->sayType(); }
};
2.派生クラスで using を使って,名前が存在することを明示する.
template<typename ElemType>
class InheritedStorageA2 : public BaseStorage <ElemType> {
public:
  using BaseStorage<ElemType>::sayType;
  void clarifyType() { sayType(); }
};
3.派生クラスの関数呼び出しで,関数名をきちんと修飾する.
template<typename ElemType>
class InheritedStorageA3 : public BaseStorage <ElemType> {
public:
  void clarifyType() { BaseStorage<ElemType>::sayType(); }
};

ということで,今日のサンプルコードです.コンパイルエラーになる箇所は,コメントアウトしています.


#include <iostream>

class ElementA {
public:
  void sayType() { std::cout << "ElementA" << std::endl; }

};

class ElementB {
public:

};

template<typename ElemType>
class BaseStorage {
public:
  void sayType() { 
    ElemType e;
    e.sayType();
  }
};

// テンプレート特化を使うと,sayTypeを持たないBaseStorageの定義が可能になる.
template<>
class BaseStorage<ElementB> {
};

/** これはNG
template<typename ElemType>
class InheritedStorageANG : public BaseStorage <ElemType> {
public:
  void clarifyType() { sayType(); }
};
**/

template<typename ElemType>
class InheritedStorageA1 : public BaseStorage <ElemType> {
public:
  void clarifyType() { this->sayType(); }
};

template<typename ElemType>
class InheritedStorageA2 : public BaseStorage <ElemType> {
public:
  using BaseStorage<ElemType>::sayType;
  void clarifyType() { sayType(); }
};

template<typename ElemType>
class InheritedStorageA3 : public BaseStorage <ElemType> {
public:
  void clarifyType() { BaseStorage<ElemType>::sayType(); }
};

int main(int, char**) {

  std::cout << "chap_7th_43_1.cpp" <<  std::endl;
  /**  これはNG!
  InheritedStorageANG<ElementA> storageANG;
  storageANG.clarifyType();
  **/

  InheritedStorageA1<ElementA> storageA1;
  storageA1.clarifyType();

  InheritedStorageA2<ElementA> storageA2;
  storageA2.clarifyType();

  InheritedStorageA3<ElementA> storageA3;
  storageA3.clarifyType();

  /** これはNG!
  InheritedStorageA3<ElementB> storageA3_B;
  storageA3_B.clarifyType();
  **/

  return 0;
}

おまけ.

上記の例は,オブジェクトファイルを生成する時点でコンパイラが BaseStorage に sayType() が存在するかどうかわからないことから発生します.逆に,下記のような書き方だとコンパイルエラーになりません.


#include <iostream>

class ElementA {
public:
  void sayType() { std::cout << "ElementA" << std::endl; }

};

template<typename ElemType>
class BaseStorage {
public:
  void sayType() { 
    ElemType e;
    e.sayType();
  }
};


// この時点でコンパイラは BaseStorage のテンプレートパラメータが ElementA ということがわかるので,基底クラスに sayType() が存在することを保証できる.
class InheritedStorageA : public BaseStorage <ElementA> {
public:
  void clarifyType() { sayType(); }
};

int main(int, char**) {

  std::cout << "chap_7th_43_2.cpp" <<  std::endl;
  InheritedStorageA storageA;
  storageA.clarifyType();

  return 0;
}