Понимание (простой?) Специализация по частичным шаблонам на С++

Примечание: это, по-видимому, является отправкой проблемы: С++ - перегружает шаблонный метод класса с частичной спецификацией этого метода

У меня возникла проблема, с которой я сталкиваюсь с специализацией по шаблону С++ вплоть до простого случая.

Он состоит из простого двухпараметрического шаблона класса 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_);
}

Ответы

Ответ 1

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

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) говорится:

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

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

С++ - перегружает шаблонный метод класса с частичной спецификацией этого метода


Итак, каково решение? Вы бы частично специализировали свой класс вместе со всей повторяющейся работой?

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

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

Ответ 2

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

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

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