Метафункция шаблона С++ для созданного объекта класса шаблона

Я надеюсь, что этот вопрос не слишком запутанный. Я понимаю, что метапрограммирование воздействует на типы, а не на объекты этих типов; тем не менее, я все еще пытаюсь добиться того же результата, 1) извлекая информацию о типе из класса, а затем 2) метафункции для этой информации о типе.

Объяснение моей ситуации с упрощенными выдержками кода выглядит следующим образом:

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

template <typename Type, uint32_t rows_ = 1, uint32_t cols_ = 1,>
class Matrix_Base{
/* 
...
*/
};

Матрица размера времени выполнения обозначается аргументом 0.

Проверка размера матрицы во время выполнения и во время компиляции довольно проста (используя boost::mpl):

            typedef typename mpl::if_<
                typename mpl::or_<
                typename mpl::equal_to<
                typename mpl::int_<rows_>::type,
                mpl::int_<0>
                >::type,
                typename mpl::equal_to <
                typename mpl::int_<cols_>::type,
                mpl::int_<0>
                >
                >::type,
                mpl::true_,
                mpl::false_
                >::type runtime_size_type;

Это было протестировано и работает нормально. Моя беда начинается примерно здесь...

В настоящее время я использую приведенный выше код boost::mpl следующим образом:

namespace internal {
template <uint32_t rows = 1, uint32_t cols_ = 1>
struct Runtime_Size_Helper {
typedef typename mpl::if_< 
// REST OF THE BOOST::MPL code here //
>::type runtime_size_t
bool value() { return runtime_size_t::value;}
};
} // namespace
template <typename Type, uint32_t rows_ = 1, uint32_t cols_ = 1>
class Matrix_Base{
// ...
static constexpr uint32_t rows = rows_;
static constexpr uint32_t cols = cols_;
bool is_runtime_sized;
// ...
};

template <typename T, uint32_t R, uint32_t C>
bool Matrix_Base<T,R,C>::is_runtime_sized = internal::Runtime_Size_Helper<R,C>::value();

Это превращает результат этой функции mpl в член класса Matrix_Base. Все идет нормально.

Я хотел бы использовать некоторую форму косвенности, чтобы определить значение runtime_size_type, передав ему созданный объект. Согласно коду примера, единственной необходимой информацией для определения этого являются параметры uint32_t для столбца и строки.

Для созданного объекта Matrix_Base соответствующая информация никогда не изменится по сравнению со значениями типа компиляции. Размер матрицы будет неизменным; размер будет либо установлен из аргументов шаблона, либо -- для матриц размера времени выполнения -- через конструктор. В обоих случаях аргументы шаблона фиксированы и являются частью информации о типе. Я сохраняю эту информацию как статические переменные в классе, и я даже пытался добавить typedef со всеми параметрами шаблона как my_type typename typename Matrix_Base<T,rows_, cols_, ...> my_type, но я не могу понять, как написать метафункцию, которой я могу передать объект Matrix_Base (очевидно, как ссылка или указатель) и повторно извлечь соответствующую информацию.

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

Надеюсь, что это понятно. Пожалуйста, дайте мне знать, если здесь есть что-то неясное или просто глупое.

С уважением, Шмуэль

отредактировал текст, чтобы внести немного больше ясности в вопрос


person Shmuel Levine    schedule 13.10.2013    source источник


Ответы (1)


Самый быстрый способ сделать то, что вы, кажется, хотите (на самом деле вы не указали это подробно), — это позволить вашему шаблону матричного класса наследоваться от шаблона класса хранения и специализировать класс хранения в зависимости от того, возвращает ли ваш предикат MPL значение true или ложный

#include <array>
#include <iostream>
#include <vector>
#include <boost/mpl/bool.hpp>
#include <boost/mpl/comparison.hpp>
#include <boost/mpl/if.hpp>
#include <boost/mpl/int.hpp>
#include <boost/mpl/logical.hpp>

using namespace boost;

// in C++98, use 
// template<int R, int C>
// struct is_runtime_sized: mpl::if<
// ...
// >::type {};
template<int R, int C>
using is_runtime_sized = typename mpl::if_<
    mpl::or_<
        mpl::equal_to<mpl::int_<R>, mpl::int_<0>>,
        mpl::equal_to<mpl::int_<C>, mpl::int_<0>>
    >,
    mpl::true_, mpl::false_
>::type;

Обратите внимание, что я удалил некоторые ненужные typename вхождения, чтобы сделать предикат MPL более читабельным.

template<class T, int R, int C, bool = is_runtime_sized<R, C>::value>
struct MatrixStorage
{
    MatrixStorage() = default;
    MatrixStorage(int r, int c): data_(r * c) {} // zero-initializes
protected:    
    std::vector<T> data_;
};

template<class T, int R, int C>
struct MatrixStorage<T, R, C, false>
{
    MatrixStorage() = default;
    MatrixStorage(int, int): data_{} {} // zero-initializes
protected:    
    std::array<T, R * C> data_;    
};

Здесь я разделил реализацию динамически и статически хранимых матриц. В первом используется std::vector, а во втором - std:array. Подобно Eigen, у обоих есть конструктор по умолчанию, и у обоих также есть конструктор, принимающий размеры матрицы, которые инициализируются нулем.

template<class T, int R = 0, int C = 0>
struct Matrix: public MatrixStorage<T, R, C>
{
    Matrix() = default;
    // In C++98, write:
    // Matrix(int r, int c): MatrixStorage<T, R, C>(r, c) {}
    using MatrixStorage<T, R, C>::MatrixStorage;
    int size() const { return this->data_.size(); }
};

Фактический класс Matrix наследуется от MatrixStorage и возвращает size() в зависимости от текущих сохраненных данных.

int main()
{
    Matrix<int> m_dyn(3, 3);
    std::cout << m_dyn.size() << "\n"; // 9

    Matrix<int, 2, 2> m_stat;
    std::cout << m_stat.size() << "\n"; // 4
}

Живой пример. Как вы можете видеть, динамически размещаемая матрица имеет размер 9, а матрица со статическим размером имеет размер 4. Обратите внимание, что в приведенном выше коде есть две небольшие функции C++11, которые вы можете легко обойти, если вам потребуется используйте С++11.

person TemplateRex    schedule 14.10.2013
comment
еще раз спасибо за ваш ответ. Я предполагаю, что почему мой вопрос был не ясен, как вы сказали. Это не было связано с хранением (хотя это было разумное и разумное предположение). На самом деле, прочитав ваш ответ, я понял, что у меня было несколько похожих проблем, и все они выиграют от этого подхода. - person Shmuel Levine; 14.10.2013
comment
@ShmuelLevine, важным моментом является то, что объекты времени выполнения не могут обрабатываться во время компиляции. Другой способ обработки кода времени выполнения/компиляции - использовать constexpr, но этот подход действительно хорошо работает только с C++14 (в настоящее время поддерживается только транком Clang SVN). - person TemplateRex; 14.10.2013
comment
В этом есть смысл. В моем случае я использую constexpr, и вся необходимая информация доступна во время компиляции, но я использую C++11, а не C++14. В любом случае, ваше предложение выше решит ряд проблем, которые я пытался решить. - person Shmuel Levine; 15.10.2013
comment
@ShmuelLevine, вы можете использовать некоторые простые вещи constexpr в C++11, такие как конструктор, который хранит динамический размер. Я больше думал о матричных функциях constexpr, для которых рекурсивная структура стиля C++11 constexpr была бы слишком громоздкой. - person TemplateRex; 15.10.2013