web-dev-qa-db-ja.com

(単純?)C ++部分テンプレートの特殊化を理解する

注:これは問題の再投稿のようです: C++-テンプレート化されたクラスメソッドをそのメソッドの部分的な仕様でオーバーロードします

私は、C++テンプレートの特殊化で抱えている問題を単純なケースに要約しました。

これは、単純な2パラメーターのテンプレートクラスThingで構成されています。ここで、Thing<A,B>::doSomething()B=intに特化したいと思います。

#include <cstdio>

//
// A 3-parameter template class.
//
template <class A, class B>
class Thing
{
public:
    Thing(A a, B b) : a_(a), b_(b) {}
    B doSomething();
private:
    A a_;
    B b_;
};

//
// The generic case works as expected.
//
template <class A, class B>
B Thing<A,B>::doSomething()
{
    return b_;
}

//
// This specialization does not work!
//
template <class A>
int Thing<A,int>::doSomething()
{
    return b_+1;
}

int main( int argc, char** argv )
{
    // Setup our thing.
    Thing<double,int> thing(1.0,2);
    // This doesn't compile - but works with the generic case.
    printf("Expecting 3, and getting %i\n", thing.doSomething());
    // Clean up.
    return 0;
}

残念ながら、g++は次のエラーで終了します。

partial_specialization.cpp:30: error: invalid use of incomplete type ‘class Thing<A, int>’
partial_specialization.cpp:8: error: declaration of ‘class Thing<A, int>’

clang++コンパイラはもう少し冗長ですが、同じ問題があります。

partial_specialization.cpp:30:19: error: nested name specifier 'Thing<A, int>::' for declaration does not
      refer into a class, class template or class template partial specialization
int Thing<A,int>::doSomething()
    ~~~~~~~~~~~~~~^
partial_specialization.cpp:32:12: error: use of undeclared identifier 'b_'
    return b_+1;
           ^
2 errors generated.

関数の部分的なテンプレートの特殊化は許可されていないことを読んで理解しましたが、この場合はThingのクラスを部分的に特殊化していると思いました。

何か案は?

私がしたこと:受け入れられた回答によって提供されたリンクから決定された回避策:

template< class T >
inline T foo( T const & v ) { return v; }

template<>
inline int foo( int const & v ) { return v+1; }

//
// The generic case works as expected.
//
template <class A, class B>
B Thing<A,B>::doSomething()
{
    return foo(b_);
}
20
Dan

メンバー関数テンプレートであろうとスタンドアロン関数テンプレートであろうと、関数テンプレートの部分的な特殊化は、標準では許可されていません。

_template<typename T, typename U> void f() {} //okay  - primary template
template<typename T> void f<T,int>() {}      //error - partial specialization
template<> void f<unsigned char,int>() {}    //okay  - full specialization
_

ただし、クラステンプレート自体を部分的に特殊化することはできます。あなたはこのようなことをすることができます:

_template <class A>
class Thing<A,int>  //partial specialization of the class template
{
    //..
    int doSomething();
};

template <class A>
int Thing<A,int>::doSomething()  { /* do whatever you want to do here */ }
_

クラステンプレートを部分的に特殊化する場合、メンバー関数のテンプレートパラメータリスト(クラス外の定義内)、はテンプレートと一致する必要があることに注意してくださいクラステンプレートの部分的な特殊化のパラメータリスト。つまり、クラステンプレートの上記の部分的な特殊化では、これを定義することはできません。

_template <class A>
int Thing<A,double>::doSomething(); //error
_

関数定義のテンプレートパラメータリストがクラステンプレートの部分特殊化のテンプレートパラメータリストと一致しなかったため、許可されていません。規格(2003)の§14.5.4.3/ 1は次のように述べています。

クラステンプレートの部分的特殊化のメンバーのテンプレートパラメータリストは、クラステンプレートの部分的特殊化のテンプレートパラメータリストと一致する必要があります。[...]

これについての詳細は、ここで私の答えを読んでください:

C++-テンプレート化されたクラスメソッドをそのメソッドの部分的な仕様でオーバーロードします


では、解決策は何ですか?すべての反復作業とともに、クラスを部分的に専門化しますか?

簡単な解決策は、クラステンプレートを部分的に特殊化するのではなく、作業の委任です。 スタンドアロン関数テンプレートを作成し、これを次のように特殊化します。

_template <class B>
B doTheActualSomething(B & b) { return b;  }

template <>
int doTheActualSomething<int>(int & b) { return b + 1; }
_

次に、この関数テンプレートをdoSomething()メンバー関数から次のように呼び出します。

_template <class A, class B>
B Thing<A,B>::doSomething() { return doTheActualSomething<B>(b_); }
_

特定のケースでは、doTheActualSomething1つだけメンバー、つまり_b__の値を知る必要があるため、タイプが引数として関数に値を渡すことができるため、上記のソリューションで問題ありません。はテンプレートですtype引数Bであり、完全な特殊化であればintの特殊化が可能です。

しかし、複数のメンバーにアクセスする必要がある場合を想像してみてください。それぞれのtypeはテンプレートに依存しますtype引数リスト、スタンドアロン関数テンプレートを定義しても問題は解決しません、関数テンプレートに複数のtype引数があり、部分的に関数をたとえば1つだけに特化することはできないためtype =(許可されていないため)。

したがって、この場合、代わりにクラステンプレートを定義できます。これは、静的な非テンプレートメンバー関数doTheActualSomethingを定義します。方法は次のとおりです。

_template<typename A, typename B>
struct Worker
{
   B doTheActualSomething(Thing<A,B> *thing)
   {
      return thing->b_;
   }
};

//partial specialization of the class template itself, for B = int
template<typename A>
struct Worker<A,int>
{
   int doTheActualSomething(Thing<A,int> *thing)
   {
      return thing->b_ + 1;
   }
};
_

thingポインターを使用して、クラスの任意のメンバーにアクセスできることに注意してください。もちろん、プライベートメンバーにアクセスする必要がある場合は、次のように_struct Worker_をThingクラステンプレートのフレンドにする必要があります。

_//forward class template declaration
template<typename T, typename U> struct Worker

template <class A, class B>
class Thing
{
    template<typename T, typename U>  friend struct Worker; //make it friend
   //...
};
_

次に、次のように作業を友人に委任します。

_template <class A, class B>
B Thing<A,B>::doSomething()
{
    return Worker<A,B>::doTheActualSomething(this); //delegate work
}
_

ここで注意すべき2つのポイント:

  • このソリューションでは、doTheActualSomethingはメンバー関数ではありませんtemplate。テンプレートであるクラスを含まない。したがって、partiallyはいつでもクラステンプレートを特殊化して、partialメンバー関数テンプレートの特殊化の望ましい効果を得ることができます。
  • 関数への引数としてthisポインターを渡すので、_Thing<A,B>_もフレンドであるため、プライベートメンバーも含めてクラス_Worker<T,U>_の任意のメンバーにアクセスできます。

完全なオンラインデモ: http://www.ideone.com/uEQ4S


今でも改善の可能性があります。これで、Workerクラステンプレートのすべてのインスタンス化は、Thingクラステンプレートのすべてのインスタンス化のフレンドになります。したがって、この多対多の友情を次のように制限できます。

_template <class A, class B>
class Thing
{
    friend struct Worker<A,B>; //make it friend
   //...
};
_

これで、Workerクラステンプレートの1つのインスタンス化のみが、Thingクラステンプレートの1つのインスタンス化のフレンドになります。それは1対1の友情です。つまり、_Worker<A,B>_は_Thing<A,B>_の友達です。 _Worker<A,B>_は_Thing<A,C>_の友達ではありません。

この変更により、コードを多少異なる順序で記述する必要があります。クラスと関数の定義のすべての順序とすべてを含む完全なデモを参照してください。

http://www.ideone.com/6a1Ih

41
Nawaz

これは非常に頻繁に見られる問題であり、驚くべきことに単純な解決策があります。コードを使用するよりも明確であり、コードに適合させるには理解する必要があるため、人為的な例で示します。

template<typename A, typename B>
struct TwoTypes { };

template<typename A, typename B>
struct X {
  /* forwards ... */
  void f() { fImpl(TwoTypes<A, B>()); }

  /* special overload for <A, int> */
  template<typename A1>
  void fImpl(TwoTypes<A1, int>) {
    /* ... */
  }

  /* generic */
  template<typename A1, typename B1>
  void fImpl(TwoTypes<A1, B1>) {
    /* ... */
  }
};

関数を明示的に特殊化することは決して(ほとんど決して?)正しい方法ではありません。プログラマーとしての私の仕事では、関数テンプレートを明示的に特殊化したことはありません。オーバーロードと半順序が優れています。