Шаблоны c ++: проблема со специализацией членов - PullRequest
1 голос
/ 26 марта 2010

Я пытаюсь создать шаблон «Автокласс», который создает произвольный класс с произвольным набором членов, например:

AutoClass<int,int,double,double> a;
a.set(1,1);
a.set(0,2);
a.set(3,99.7);
std::cout << "Hello world! " << a.get(0) << " " << a.get(1) << " " << a.get(3) << std::endl;

Теперь у меня есть автокласс с рабочим элементом set:

class nothing {};

template <  typename T1 = nothing, typename T2 = nothing, typename T3 = nothing,
            typename T4 = nothing, typename T5 = nothing, typename T6 = nothing>
class AutoClass;

template <>
class AutoClass<nothing, nothing, nothing,
                nothing, nothing, nothing>
{
    public:
    template <typename U> void set(int n,U v){}
};

template <  typename T1, typename T2, typename T3,
            typename T4, typename T5, typename T6>
class AutoClass: AutoClass<T2,T3,T4,T5,T6>
{
    public:
    T1 V;
    template <typename U> void set(int n,U v)
    {
        if (n <= 0)
            V = v;
        else
            AutoClass<T2,T3,T4,T5,T6>::set(n-1,v);
    }
};

и у меня начались проблемы с реализацией соответствующего «get». Этот подход не компилируется:

template <  typename T1, typename T2, typename T3,
            typename T4, typename T5, typename T6>
class AutoClass: AutoClass<T2,T3,T4,T5,T6>
{
    public:
    T1 V;
    template <typename U> void set(int n,U v)
    {
        if (n <= 0)
            V = v;
        else
            AutoClass<T2,T3,T4,T5,T6>::set(n-1,v);
    }
    template <typename W> W get(int n)
    {
        if (n <= 0)
            return V;
        else
            return AutoClass<T2,T3,T4,T5,T6>::get(n-1);
    }
    template <> T1 get(int n)
    {
        if (n <= 0)
            return V;
        else
            return AutoClass<T2,T3,T4,T5,T6>::get(n-1);
    }
};

Кроме того, похоже, мне нужно реализовать get для специализации <nothing, nothing, nothing, nothing, nothing, nothing>. Любая идея о том, как решить эту проблему?

Ответы [ 4 ]

3 голосов
/ 26 марта 2010

Прежде всего, я предпочитаю Boost.Fusion вместо Boost.Tuple, так как он поддерживает лучшее сочетание шаблонного метапрограммирования и алгоритмов выполнения, как мне кажется.

Например, я хотел бы представить вам небольшое чудо:

struct Name {}; extern const Name name;
struct GivenName {}; extern const GivenName givenName;
struct Age {}; extern const Age age;

class Person
{
public:
  template <class T>
  struct value
  {
    typedef typename boost::fusion::result_of::at_key<data_type const,T>::type type;
  };

  template <class T>
  struct has
  {
    typedef typename boost::fusion::result_of::has_key<data_type,T>::type type;
  };

  template <class T>
  typename value<T>::type
  get(T) { return boost::fusion::at_key<T>(mData); }

  template <class T>
  Person& set(T, typename value<T>::type v)
  {
    boost::fusion::at_key<T>(mData) = v; return *this;
  };

private:
  typedef boost::fusion::map <
    std::pair<Name, std::string>,
    std::pair<GivenName, std::string>,
    std::pair<Age, unsigned short>
  > data_type;
  data_type mData;
};

Это действительно весело использовать:

Person p;
p.set(name, "Rabbit").set(givenName, "Roger").set(age, 22);

Ну, я сам предпочитаю индексирование по классам, а не по индексам, потому что я могу передать смысл, а также добавить проверку типов;)

3 голосов
/ 26 марта 2010

Могу ли я порекомендовать использовать обширный (и хорошо протестированный и кроссплатформенный) набор классов-магических шаблонов библиотеки Boost? Похоже, что вы ищете это boost :: tuple . В любое время вы можете избежать написания собственного кода - особенно в сложной ситуации с шаблонами - вы должны использовать чужой код.

1 голос
/ 26 марта 2010

Как уже упоминалось, вы, вероятно, сможете получить то, что вы хотите, повторно используя существующие реализации из Boost или в другом месте.

Если вы будете делать что-то, что нельзя сделать, используя это, или если вам интересно:

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

Простой подход, использующий MPL для удобства, может выглядеть примерно так:

template<class Types, size_t N> struct holder 
  // recursively derive from holder types:
  : holder<Types, N-1> 
{
    typename boost::mpl::at_c<Types,N>::type value;
};

// specialization that terminates the recursive derivation:
template<class Types> struct holder<Types,0> {
    typename boost::mpl::at_c<Types,0>::type value;
};

template<class Types>
class AutoClass 
  // recursively derive from holder types:
  : holder<Types, boost::mpl::size<Types>::value-1>    
{
    enum { n = boost::mpl::size<Types>::value };
public:
    template<size_t N, class U> void set(const U& u) {
        // index check at compile time:
        BOOST_STATIC_ASSERT((N < n));
        // cast to responsible holder base:
        static_cast<holder<Types,N>*>(this)->value = u;
    }
    template<size_t N> typename boost::mpl::at_c<Types,N>::type get() const { 
        // index check at compile time:
        BOOST_STATIC_ASSERT((N < n));
        // cast to responsible holder base:
        return static_cast<const holder<Types,N>*>(this)->value;
    } 
};

Использование:

typedef boost::mpl::vector<int,std::string> Types;
AutoClass<Types> a;

a.set<0>(42);
assert(a.get<0>() == 42);
a.set<1>("abcde");
assert(a.get<1>() == "abcde");

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

0 голосов
/ 26 марта 2010

Вам нужно реализовать для <ничего, ничего ...> из-за вашего базового случая. Рассмотрим:

</p>

<pre><code>template <typename W> W get(int n)
{
    if (n <= 0)
        return V;
    else
        return AutoClass<T2,T3,T4,T5,T6>::get(n-1);
}

Рассмотрим, что происходит, когда вы вызываете эту функцию для полного автокласса с n из 5. Он создает автокласс с 5 членами и вызывает с n = 4 .... и снова, пока не достигнет этой точки:

</p>

<pre><code>template <typename W> W get(int n) // current autoclass is <T6,nothing,nothing...>
{
    if (n <= 0)
        return V;
    else
        return AutoClass<T2,T3,T4,T5,T6>::get(n-1); // this is <nothing, nothing...>
}

Конечно, вызов этого автокласса не произойдет, но компилятор должен все равно скомпилировать этот код, потому что вы сказали ему.

Вам также нужно сделать автокласс :: get, потому что n может быть 1093.

Я не вижу выхода из этого с вашим текущим интерфейсом. Если вы введете n в аргумент шаблона, вы можете создать специальный случай, который бы этого не делал. В этом случае вы не можете. Я думаю, что вы столкнетесь с множеством проблем, потому что вы выбрали этот интерфейс, который будет довольно сложно решить. Например, что происходит, когда W - это int, а AutoClass :: get (n-1) возвращает удвоение или хуже, что-то совершенно несовместимое?

...