Понимание (просто?) C ++ Частичная специализация шаблонов - PullRequest
18 голосов
/ 26 мая 2011

Примечание: кажется, что это повторная проблема: 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_);
}

Ответы [ 2 ]

39 голосов
/ 26 мая 2011

Частичная специализация шаблона функции, будь то шаблон функции-члена или автономный шаблон функции, не допускается Стандартом:

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

Это недопустимо, поскольку список параметров шаблона в определении функции не соответствует списку параметров шаблона частичной специализации шаблона класса. §14.5.4.3 / 1 из Стандарта (2003) гласит:

Список параметров шаблона члена частичной специализации шаблона класса должен соответствовать Список параметров шаблона частичной специализации шаблона класса. [...]

Подробнее об этом читайте в моем ответе здесь:

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_); }

Поскольку в вашем конкретном случае doTheActualSomething необходимо знать значение только одного члена, а именно b_, вышеприведенное решение подойдет, так как вы можете передать значение функции в качестве аргумента чей тип является шаблоном тип аргумент B, а специализация для int возможна при полной специализации.

Но представьте, что если требуется доступ к нескольким элементам, тип каждого зависит от шаблона тип аргумент-список, то определение шаблона автономной функции не решит проблема, потому что теперь в шаблоне функции будет более одного типа аргумента, и вы не сможете частично специализировать функцию для, скажем, одного типа ( как это не допускается).

Таким образом, в этом случае вы можете вместо этого определить шаблон класса, который определяет статическую не шаблонную функцию-член 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
}

Здесь следует отметить два момента:

  • В этом решении doTheActualSomething не является функцией-членом template . Это не включающий класс, который является шаблоном. Следовательно, мы можем частично специализировать шаблон класса в любое время, чтобы получить желаемый эффект частичного специализации шаблона функции-члена.
  • Поскольку мы передаем указатель 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 является другом одного экземпляра шаблона класса Thing. Это дружба один на один. То есть Worker<A,B> - друг Thing<A,B>. Worker<A,B> НЕ друг Thing<A,C>.

Это изменение требует от нас написания кода в несколько ином порядке. Посмотрите полную демонстрацию со всеми порядками определений классов и функций и всеми:

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

6 голосов
/ 26 мая 2011

Это очень часто встречающаяся проблема, и есть удивительно простое решение. Я покажу это на искусственном примере, потому что он более понятен, чем использование вашего кода, и вам придется понять его, чтобы адаптировать его к вашему коду

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>) {
    /* ... */
  }
};

Явная специализация функций никогда не бывает (почти никогда?) Правильной. В моей работе программиста я никогда не специализировал шаблон функций. Перегрузка и частичный заказ превосходят.

...