Оптимизация констант времени компиляции - PullRequest
0 голосов
/ 02 марта 2012

Я пытаюсь реализовать класс, такой как std :: pair, но с более чем 2 компонентами.Поскольку в моем приложении может случиться так, что некоторые компоненты кортежа уже известны во время компиляции, я хотел бы провести следующую оптимизацию пространства: когда я знаю, что компонент является константой времени компиляции, просто объявите его как 'static const 'member, так что он не будет тратить хранилище в отдельных экземплярах класса.Спецификатор const гарантирует, что любая попытка изменить значение во время выполнения приведет к ошибке компиляции, по крайней мере, если мы исключим невежливые const_cast (s).

Я закончил со следующей реализацией, классом ntuple

template<typename T0_ = void,
     typename T1_ = void,
     typename T2_ = void,
     typename T3_ = void 
     > class ntuple;

и класс, используемый для обозначения констант времени компиляции

template<class type_, type_ value_> class constant 
{
   typedef type_ type;
   static const type value = value_;
};

и набор частичных специализаций класса ntuple

template<typename T0_>
class ntuple<
 T0_
> {
public:
static const int n=1;
typedef T0_ T0;
static const bool is_static = false;
static const bool is_static0 = false;
T0_ i0;
};

template<
typename T0_, T0_ value0_
>
class ntuple<
constant<T0_, value0_>
> {
public:
static const int n=1;
typedef T0_ T0;
static const bool is_static = true;
static const bool is_static0 = true;
static const T0_ i0 = value0_;
};

template<
typename T0_, T0_ value0_
> const T0_ ntuple<
constant<T0_, value0_> >::i0;

template<
typename T0_,
typename T1_
>
class ntuple<
T0_,
T1_
> {
public:
static const int n=2;
typedef T0_ T0;
typedef T1_ T1;
static const bool is_static = false;
static const bool is_static0 = false;
static const bool is_static1 = false;
T0_ i0;
T1_ i1;
};

template<
typename T0_,
typename T1_, T1_ value1_
>
class ntuple<
T0_,
constant<T1_, value1_>
> {
public:
static const int n=2;
typedef T0_ T0;
typedef T1_ T1;
static const bool is_static = false;
static const bool is_static0 = false;
static const bool is_static1 = true;
T0_ i0;
static const T1_ i1 = value1_;
};

template<
typename T0_,
typename T1_, T1_ value1_
> const T1_ ntuple<
T0_,
constant<T1_, value1_> >::i1;

template<
typename T0_, T0_ value0_,
typename T1_
>
class ntuple<
constant<T0_, value0_>,
T1_
> {
public:
static const int n=2;
typedef T0_ T0;
typedef T1_ T1;
static const bool is_static = false;
static const bool is_static0 = true;
static const bool is_static1 = false;
static const T0_ i0 = value0_;
T1_ i1;
};

template<
typename T0_, T0_ value0_,
typename T1_
> const T0_ ntuple<
constant<T0_, value0_>,
T1_ >::i0;

template<
typename T0_, T0_ value0_,
typename T1_, T1_ value1_
>
class ntuple<
constant<T0_, value0_>,
constant<T1_, value1_>
> {
public:
static const int n=2;
typedef T0_ T0;
typedef T1_ T1;
static const bool is_static = true;
static const bool is_static0 = true;
static const bool is_static1 = true;
static const T0_ i0 = value0_;
static const T1_ i1 = value1_;
};

template<
typename T0_, T0_ value0_,
typename T1_, T1_ value1_
> const T0_ ntuple<
constant<T0_, value0_>,
constant<T1_, value1_> >::i0;

template<
typename T0_, T0_ value0_,
typename T1_, T1_ value1_
> const T1_ ntuple<
constant<T0_, value0_>,
constant<T1_, value1_> >::i1;

Таким образом,члены, помеченные как константа <.,.>, не сохраняются как члены класса, что уменьшает размер объекта.Количество требуемой частичной специализации может быть огромным, 2 ^ N для N = 1,2,3,4. Я сообщаю только до N = 2: я написал простой скрипт для генерации всех из них.Класс можно использовать следующим образом:

ntuple<int, int, bool> tup1;
tup1.i0=2;
tup1.i1=0;
tup1.i2=true;
assert (tup1.i0==2);
assert (tup1.i1==0);
assert (tup1.i2==true);

ntuple<int, constant<int, 3>, constant<bool, false> > tup2;
tup2.i0=2;
// tup2.i1=0;  // cannot be assigned, is static a constant
// tup2.i2=true; // cannot be assigned, is static a constant 
assert (tup2.i0==2);
assert (tup2.i1==3);
assert (tup2.i2==false);

assert (sizeof(tup1)>sizeof(tup2));

Таким образом, класс работает отлично.Теперь я хотел бы только улучшить синтаксис объявления ntuples следующим образом

ntuple<int, int_<3>, bool_<true> >

вместо

ntuple<int, constant<int, 3>, constant<bool, true> >

, где int_ и bool_ могут быть определены как

template<int i> struct int_ : constant<int, i> {};
template<bool b> struct bool_ : constant<bool, b> {};

или я мог бы вместо этого использовать аналоги boost :: mpl, это не главное.Для достижения этого простое решение состоит в том, чтобы написать другой скрипт и сгенерировать все возможные специализации для всех перестановок постоянных и непостоянных параметров шаблона, где постоянные параметры шаблона могут быть int_, bool_, char_ и т. Д. Это возможно, но ценойфакторного увеличения числа частичных специализаций.Я думал об изменении определения класса ntuple следующим образом:

 template<typename T0_ = void,
 typename T1_ = void,
 typename T2_ = void,
 typename T3_ = void,
 bool const0 = is_const<T0_>::value,
 bool const1 = is_const<T1_>::value,
 bool const2 = is_const<T2_>::value,
 bool const3 = is_const<T3_>::value
 > class ntuple;

с

template <class T> is_const { static const bool value = false; };
template <int i> is_const<int_<i> > { static const bool value = true; };
template <bool b> is_const<bool_<b> > { static const bool value = true; };

и специализировать ntuple следующим образом

 template<typename T0_,
 typename T1_,
 typename T2_,
 typename T3_> class ntuple<T0_,T1_,T2_,T3_,false,false,false,false> { ... };

 template<typename T0_,
 typename T1_,
 typename T2_,
 typename T3_> class ntuple<T0_,T1_,T2_,T3_,true,false,false,false> { ... };

и т. Д. Этоуменьшит количество частичной специализации до того же числа, что и раньше, и требует только специализации класса признаков для каждого допустимого типа «константа».Проблема в том, что я хотел бы избежать дополнительных параметров шаблона.Я мог бы сделать это путем наследования, определив вспомогательный класс

 template<typename T0_ = void,
 typename T1_ = void,
 typename T2_ = void,
 typename T3_ = void,
 bool const0 = is_const<T0_>::value,
 bool const1 = is_const<T1_>::value,
 bool const2 = is_const<T2_>::value,
 bool const3 = is_const<T3_>::value
 > class ntuple_impl;

specialize, как указано выше, а затем

 template <class T0, class T1, class T2, class T3>
 class ntuple  : ntuple_impl<T0, T1, T2, T3, 
                 is_const<T0>::value, 
                 is_const<T1>::value, 
                 is_const<T2>::value,
                 is_const<T3>::value> { ... };

, но я хотел бы избежать наследования, поскольку результирующий объект будет больше, чемнеобходимо в некоторых случаях, потому что он будет содержать ntuple_impl в качестве подобъекта.Я бы знал, есть ли другое решение этой проблемы.Благодарю.Giuliano

1 Ответ

1 голос
/ 05 сентября 2013

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

Следуя идее Xeo, вы можете сделать именно то, что вы хотите, намного проще.Здесь я предполагаю, что вам нужен чистый c ++ 03.

Прежде всего, объявите тип, который будет использоваться просто как заполнитель

struct _;

Объявите также тип, который будет интерпретироваться как статическая константа.

template<typename T, T>
struct constant;

Обратите внимание, что ни для одного из вышеперечисленных определений не требуется.

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

template<typename T>
struct element
{
    typedef T type;
    type value;
};

template<>
struct element<_>;

Ключевым моментом является специализация element для constant и любых других типов, которые вы хотите, например, int_ и bool_, которые выцитируется.

template<typename T, T val>
struct element<constant<T, val> >
{
    typedef T const type;
    static type value = val;
};

template<typename T, T val>
typename element<constant<T, val> >::type element<constant<T, val> >::value;

С этого момента определение ntuple является простым.

template<typename T0_ = _, typename T1_ = _, typename T2_ = _, typename T3_ = _>
struct ntuple : element<T0_>, ntuple<T1_, T2_, T3_, _>
{
    typedef element<T0_> head;
    typedef ntuple<T1_, T2_, T3_, _> tail;
};

//nil
template<>
struct ntuple<_, _, _, _>
{};

Для доступа к метаданным, хранящимся в ntuple, используется метафункция get_type

template<std::size_t n, typename T>
struct get_type;

template<std::size_t n, typename T0, typename T1, typename T2, typename T3>
struct get_type<n, ntuple<T0, T1, T2, T3> > : get_type<n-1, typename ntuple<T0, T1, T2, T3>::tail>
{};

template<typename T0, typename T1, typename T2, typename T3>
struct get_type<0, ntuple<T0, T1, T2, T3> >
{
    typedef typename ntuple<T0, T1, T2, T3>::head::type type;
};

Аналогично, для доступа к данным времени выполнения используется функция get_value

template<bool cond, typename T>
struct enable_if
{
};

template<typename T>
struct enable_if<true, T>
{
    typedef T type;
};

template<std::size_t n, typename T0, typename T1, typename T2, typename T3>
typename enable_if<n, typename get_type<n, ntuple<T0, T1, T2, T3> >::type&>::type
get_value(ntuple<T0, T1, T2, T3>& tuple)
{
    return get_value<n-1>(static_cast<typename ntuple<T0, T1, T2, T3>::tail&>(tuple));
}

template<std::size_t n, typename T0, typename T1, typename T2, typename T3>
typename enable_if<!n, typename ntuple<T0, T1, T2, T3>::head::type&>::type
get_value(ntuple<T0, T1, T2, T3>& tuple)
{
    return static_cast<typename ntuple<T0, T1, T2, T3>::head&>(tuple).value;
}

template<std::size_t n, typename T0, typename T1, typename T2, typename T3>
typename enable_if<n, typename get_type<n, ntuple<T0, T1, T2, T3> >::type const&>::type
get_value(ntuple<T0, T1, T2, T3> const& tuple)
{
    return get_value<n-1>(static_cast<typename ntuple<T0, T1, T2, T3>::tail const&>(tuple));
}

template<std::size_t n, typename T0, typename T1, typename T2, typename T3>
typename enable_if<!n, typename ntuple<T0, T1, T2, T3>::head::type const&>::type
get_value(ntuple<T0, T1, T2, T3> const& tuple)
{
    return static_cast<typename ntuple<T0, T1, T2, T3>::head const&>(tuple).value;
}

Для подсчета количества элементов, хранящихся в кортеже, существует функция под названием get_size

template<std::size_t n, typename T>
struct get_size_impl;

template<std::size_t n, typename T0, typename T1, typename T2, typename T3>
struct get_size_impl<n, ntuple<T0, T1, T2, T3> > : 
        get_size_impl<n+1, typename ntuple<T0, T1, T2, T3>::tail>
{
};

template<std::size_t n>
struct get_size_impl<n, ntuple<_, _, _, _> >
{
    static std::size_t const value = n;
};

template<std::size_t n>
std::size_t const get_size_impl<n, ntuple<_, _, _, _> >::value;

template<typename T0, typename T1, typename T2, typename T3>
std::size_t get_size(ntuple<T0, T1, T2, T3> const&)
{
    return get_size_impl<0, ntuple<T0, T1, T2, T3> >::value;
}

Наконец перегрузка operator << для печати

template<typename Char, typename CharTraits, typename T0, typename T1, typename T2, typename T3>
void print_element(std::basic_ostream<Char, CharTraits>& ostream, ntuple<T0, T1, T2, T3> const& tuple)
{
    ostream << static_cast<typename ntuple<T0, T1, T2, T3>::head const&>(tuple).value;
    if(get_size(tuple) > 1)
        ostream << std::basic_string<Char, CharTraits>(", ");

    print_element(ostream, static_cast<typename ntuple<T0, T1, T2, T3>::tail const&>(tuple));
}

template<typename Char, typename CharTraits>
void print_element(std::basic_ostream<Char, CharTraits>& ostream, ntuple<_, _, _, _> const&)
{

}

template<typename Char, typename CharTraits, typename T0, typename T1, typename T2, typename T3>
std::basic_ostream<Char, CharTraits>& operator <<(std::basic_ostream<Char, CharTraits>& ostream, ntuple<T0, T1, T2, T3> const& tuple)
{
    ostream << Char('<');
    print_element(ostream, tuple);
    ostream << Char('>');
}

Следующий пример использования иллюстрирует использование ntuples и дает понять, что целевая оптимизация достигнута.

int main()
{
    ntuple<char, int, long> a;
    ntuple<constant<char, '8'>, int, constant<long, 888> > b;
    ntuple<constant<char, '9'>, constant<int, 99>, constant<long, 999> > c;

    assert(sizeof(a) > sizeof(b));
    assert(sizeof(b) > sizeof(c));

    get_value<0>(a) = '1';
    get_value<1>(a) = 10;
    get_value<2>(a) = 100;
//    get_value<0>(b) = '2';    //assignment of read-only location
    get_value<1>(b) = 20;
//    get_value<2>(b) = 200;    //assignment of read-only location
//    get_value<0>(c) = '3';    //assignment of read-only location
//    get_value<1>(c) = 30;     //assignment of read-only location
//    get_value<2>(c) = 300;    //assignment of read-only location

    std::cout << std::endl;
    std::cout << "a = " << a << ", length: " << get_size(a) << ", size in bytes: " << sizeof(a) << std::endl;
    std::cout << "b = " << b << ", length: " << get_size(b) << ", size in bytes: " << sizeof(b) << std::endl;
    std::cout << "c = " << c << ", length: " << get_size(c) << ", size in bytes: " << sizeof(c) << std::endl;
}

Этот простой случай охватывает ntuples, которые имеют максимум 4элементы.Должно быть просто расширить его на столько элементов, сколько нужно (и поддерживается используемым компилятором).Фактически, для генерации кода с использованием этого подхода не требуется никакого сценария.

...