Подтвердить что ты не робот

Специализация шаблона класса С++, без необходимости переопределять все

У меня есть шаблонный класс:

template<typename T>
class A
{
    protected:
    std::vector<T> myVector;

    public:
    /*
    constructors + a bunch of member functions here
    */
}

Я хотел бы добавить только одну функцию-член, которая будет работать только для 1 заданного типа T. Можно ли вообще это сделать, не специализируясь на классе и не переопределяя все другие уже существующие методы?

Спасибо

4b9b3361

Ответ 1

Самое простое и чистое решение - использовать static_assert() в теле метода, отклоняя другие типы, кроме выбранного (в приведенном ниже примере принимаются только целые числа):

#include <type_traits>  
#include <vector>

template <typename T>
class A
{
public:
    void onlyForInts(T t)
    {
        static_assert(std::is_same<T, int>::value, "Works only with ints!");
    }

protected:
    std::vector<T> myVector;
};

int main()
{
    A<int> i;
    i.onlyForInts(1); // works !

    A<float> f;
    //f.onlyForInts(3.14f); // does not compile !
}

OK CASE DEMO NOK CASE DEMO

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

Стандартная ссылка на С++:

§ 14.7.1 Неявное создание экземпляра [temp.inst]

  1. Если специализация шаблона функции не была явно создана или явно специализирована, специализированная спецификация шаблона неявно создается, когда специализация ссылается в контексте, для которого требуется определение функции. Если не вызов явной специализации шаблона функции или функции-члена явно специализированного шаблона класса, аргумент по умолчанию для шаблона функции или функции-члена шаблона класса неявно создается, когда функция вызывается в контексте, который требует значение аргумента по умолчанию.

  2. [Пример:

    template<class T> struct Z {
      void f();
      void g();
    };
    
    void h() {
      Z<int> a;     // instantiation of class Z<int> required
      Z<char>* p;   // instantiation of class Z<char> not required
      Z<double>* q; // instantiation of class Z<double> not required
      a.f();        // instantiation of Z<int>::f() required
      p->g();       // instantiation of class Z<char> required, and
                    // instantiation of Z<char>::g() required
    }
    

    Ничто в этом примере не требует class Z<double>, Z<int>::g() или Z<char>::f() неявно инстанцирован. - конец примера]

Ответ 2

Да, это возможно в С++ 03 с CRTP (Любопытно повторяющийся шаблон шаблона):

#include <numeric>
#include <vector>

template<typename Derived, typename T>
struct Base
{
};

template<typename Derived>
struct Base<Derived, int>
{
    int Sum() const
    {
        return std::accumulate(static_cast<Derived const*>(this)->myVector.begin(), static_cast<Derived const*>(this)->myVector.end(), int());
    }
};

template<typename T>
class A : public Base<A<T>, T>
{
    friend class Base<A<T>, T>;

protected:
    std::vector<T> myVector;

public:
    /*
    constructors + a bunch of member functions here
    */
};

int main()
{
    A<int> Foo;
    Foo.Sum();
}

Ответ 3

В качестве альтернативного решения, которое также работает в простых С++ 03 (в отличие от решений static_assert или enable_if), вы можете добавить дополнительный аргумент шаблона по умолчанию, который позволит вам иметь как специализированной и неспециализированной версии класса. Затем вы можете наследовать свою специализированную версию из неспециализированной версии.

Вот пример фрагмента:

#include <vector>

template<typename T, bool unspecialized = false>
class A
{
  protected:
    std::vector<T> myVector;

  public:
    void setVec(const std::vector<T>& vec) { myVector = vec; }
    /*
    constructors + a bunch of member functions here
    */
};

template<>
class A<int, false> : public A<int, true>
{
  public: 
   int onlyForInt() {
      return 25;
   }
};

int main() {
  // your code goes here
  std::vector<int> vec;
  A<int> a;
  a.setVec(vec);
  a.onlyForInt();
  return 0;
}

Недостатками этого решения является необходимость добавления форвардеров конструкторов, если класс имеет нетривиальные конструкторы.

Ответ 4

Метод static_assert by @PiotrS. работает красиво. Но также приятно знать, что вы можете специализировать одну функцию-член без дублирования кода. Просто дайте generic onlyForInts() пустую реализацию no-op и специализируйте ее вне класса для int

#include <vector>

template <typename T>
class A
{
public:
    void onlyForInts(T t)
    {
        // no-op
    }

protected:
    std::vector<T> myVector;
};

template<>
void A<int>::onlyForInts(int t)
{
    // works  
}

int main()
{
    A<int> i;
    i.onlyForInts(1); // works !

    A<float> f;
    f.onlyForInts(3.14f); // compiles, but does nothing !
}

Пример Live.

Этот метод пригодится, если вы хотите иметь специфическое поведение int без полного отключения общего поведения.

Ответ 5

Один из подходов, еще не приведенный в ответах, заключается в использовании стандартной библиотеки std::enable_if для выполнения SFINAE в базовом классе, который вы наследуете к основной класс, который определяет соответствующие функции-члены.

Пример кода:

template<typename T, class Enable = void>
class A_base;

template<typename T>
class A_base<T, typename std::enable_if<std::is_integral<T>::value>::type>{
    public:
        void only_for_ints(){/* integer-based function */}
};

template<typename T>
class A_base<T, typename std::enable_if<!std::is_integral<T>::value>::type>{
    public:
        // maybe specialize for non-int
};

template<typename T>
class A: public A_base<T>{
    protected:
        std::vector<T> my_vector;
};

Этот подход будет лучше, чем пустая функция, потому что вы более строго относитесь к вашему API и лучше, чем static_cast, потому что он просто не будет попадать внутрь функции (она не будет существовать) и даст вам хорошее сообщение об ошибке во время компиляции (GCC показывает "на моей машине не имеет имени с именем" only_for_ints ").

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

(разве вы не смеете утверждать, что требование С++ 11 является нисходящим, мы в 2014 году бог-damnit, и следующий стандарт уже завершен уже!)

Кроме того, я заметил, вам, вероятно, придется определять my_vector в базовом классе вместо окончательного, потому что вы, вероятно, захотите обработать эти данные в функции-члене.

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

Пример:

template<typename T>
class base_data{
    protected:
        std::vector<T> my_vector;
};

template<typename T>
class A_base<T, typename std::enable_if<std::is_integral<T>::value>::type>: public base_bata<T>{
    public:
        void only_for_ints(){/* phew, finally. fiddle around with my_vector! */}
};

// non-integer A-base

template<typename T>
class A: public A_base<T>{
    protected:
        // helper functions not available in base
};

Это оставляет ужасную схему множественного наследования, но она очень работоспособна и упрощает определение членов на основе параметров шаблона (для будущей проверки).

Люди часто не любят множественное наследование или как выглядит сложный/грязный SFINAE, но я не мог жить без него сейчас, когда я знаю об этом: скорость статического кода с полиморфизмом динамического кода!

Ответ 6

Не уверен, где я нашел это, но вы можете использовать = delete; в качестве определения функции внутри класса, тем самым удаляя функцию для общего случая, а затем явно специализироваться вне класса:

template <typename T>
struct A
{
  auto int_only(T) -> void = delete;
};

template <> auto A<int>::int_only(int) -> void {}

int main()
{
  auto a_int = A<int>{};
  auto a_dbl = A<double>{};
  a_int.int_only(0);
  // a_dbl.int_only(3.14);  error: call to deleted member function
}

https://en.cppreference.com/w/cpp/language/function#Deleted_functions