Шаблонные классы со специализированными конструкторами - PullRequest
5 голосов
/ 11 августа 2011

Рассмотрим следующий надуманный пример определения шаблонного массива:

template <typename t, unsigned int n> class TBase
{
protected:
    t m_Data[n];

    //...
};

template <typename t, unsigned int n> class TDerived : public TBase<t, n>
{
    TDerived()
    {
    }
};

Я могу специализировать этот тип для предоставления конструктора не по умолчанию для массива длины 2 следующим образом:

template <typename t> class TDerived<t, 2> : public TBase<t, 2>
{
public:
    TDerived(const t& x0, const t& x1)
    {
        m_Data[0] = x0;
        m_Data[1] = x1;
    }
};

int main()
{
    TDerived<float, 2> Array2D_A(2.0f, 3.0f); //uses specialised constructor
    TDerived<float, 3> Array3D_A;             //uses default constructor

    return 0;
}

Есть ли какой-то другой способ, которым я могу создать класс, который имеет различные параметры конструктора, ограниченные параметрами шаблона во время компиляции, без необходимости полной специализации класса для каждого варианта?

Другими словами, есть ли какой-то способ, которым я могу иметь специализированные конструкторы в классе TBase без необходимости промежуточного шага создания TDerived при сохранении функциональности TBase?

Ответы [ 5 ]

2 голосов
/ 11 августа 2011

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

template <typename T, int N> class Foo
{
    Foo(); // general
    template <typename U> Foo<U, 2>(); // specialized, NOT REAL CODE
};

Это не работает. Вы всегда должны специализироваться на весь класс. Причина проста: вы должны знать полный тип класса сначала до , вы даже знаете, какие функции-члены существуют. Рассмотрим следующую простую ситуацию:

template <typename  T> class Bar
{
  void somefunction(const T&);
};

template <> class Bar<int>
{
  double baz(char, int);
};

Теперь Bar<T>::somefunction() зависит от T, но только функция существует , когда T не int, потому что Bar<int> - это совершенно другой класс.

Или рассмотрите даже другую специализацию template <> class Bar<double> : public Zip {}; - даже полиморфная природа класса в специализации может быть совершенно иной!

Таким образом, единственный способ предоставить специализации новые объявления членов, включая конструкторы, - это специализация всего класса. (Вы можете специализировать определение существующих функций, см. Ответ @ Alf.)

1 голос
/ 11 августа 2011

Поскольку конструктор является функцией, вам необходимо полностью специализировать содержащий класс для решения вашей конкретной проблемы.Выхода нет.

Однако функции не могут быть частично специализированными (во всех компиляторах).Поэтому предположим, что если вам известно, что вам нужно n = 2, когда t = int or double, то следующим вариантом является

template<>
TDerived<int,2>::TDerived()
{
  //...
}
template<>
TDerived<double,2>::TDerived()
{
  //...
}

и т. Д.

[Примечание: если вы используете MSVC, то ядумаю, что это поддерживает частичную специализацию;в этом случае вы можете попробовать:

template<typename t>
TDerived<t,2>::TDerived()
{
  //...
}

, хотя я не уверен в этом.]

1 голос
/ 11 августа 2011

Вы всегда можете специализировать участника, например,

#include <stdio.h>

template< class Type >
struct Foo
{
    void bar() const
    { printf( "Single's bar.\n" ); }
};

template<>
void Foo< double >::bar() const
{ printf( "double's bar.\n" ); }

int main()
{
    Foo<int>().bar();
    Foo<double>().bar();
}

Но вы хотите эффективно разные подписи, так что это не случай специализации члена.

Одним из способов продвижения вперед является объявление конструктора с одним аргументом, тип которого зависит от параметров шаблона.

Тогда вы можете специализировать это, как хотите.

Приветствия & hth.,

1 голос
/ 11 августа 2011

Для этого я вижу в основном два варианта:

  • Использовать переменную функцию для построения (т. Е. "..." нотацию), вы можете использовать значение n внутри этогофункция, чтобы получить ваши аргументы из стека.Тем не менее, компилятор не будет проверять во время компиляции, если пользователь предоставит правильное количество аргументов.

  • Используйте серьезную магию шаблона, чтобы разрешить инициализацию изменения вызова, которая будет выглядеть следующим образом:vector(2.0f)(3.0f).Вы можете создать что-то, что, по крайней мере, гарантирует, что пользователь не предоставит здесь слишком много аргументов.Несмотря на то, что механизм немного сложнее, я могу собрать пример, если хотите.

0 голосов
/ 11 августа 2011

Вы можете дать наиболее распространенные определения в неспециализированном классе и static_assert (BOOST_STATIC_ASSERT для не C ++ 0x) для длины массива. Это можно считать взломом, но это простое решение вашей проблемы и безопасно.

template<typename T, unsigned int n>
struct Foo {
  Foo(const T& x) { static_assert(n == 1, "Mooh!"); }
  Foo(const T& x1, const T& x2) { static_assert(n == 2, "Mooh!"); }
};

«Злой» путь - это различные аргументы.

template<typename T, unsigned int n>
struct Foo {
  Foo(...) { 
    va_list ap;
    va_start(ap, n);
    for(int j=0; j < n; ++j)
      bork[j] = va_arg(ap, T);
    va_end(ap);
  }
};

Также есть C ++ 0x и старый добрый трюк make_something, который сложнее, чем можно подумать.

template<typename... T, unsigned int n>
Foo<T, n> make_foo(T&&...) {
  // figure out the common_type of the argument list
  // to our Foo object with setters or as a friend straight to the internals
  Foo< std::common_type< T... >::type, sizeof(T) > foo;
  // recursive magic to pick the list apart and assign 
  // ...
  return foo;
}
...