пятница, 11 ноября 2011 г.

Китайская грамота

Сегодня нашёл способ заставить шаблонную функцию определять размер массива.

Всё началось с того, что при программировании я использовал несколько вспомагательных шаблонных функций, вроде memzero(), которая является обёрткой над memset() и принимает один параметр вместо трёх:
template < typename T >
inline void *memzero(T *p)
{
  return memset(p, 0, sizeof(T));
}
Также я завёл один вспомогательный макрос для очистки статических С-arrays:
#define zeroarray(a) memset(a, 0, sizeof(a));
Мне хотелось оформить этот макрос тоже в виде функции, но вся загвоздка в том, что определив параметр как указатель на тип, применить к нему sizeof() нельзя. Первая попытка оформить функцию в виде шаблона выглядила так:
template < typename T, size_t N >
inline void zeroarray(T a[N])
{
    return memset(a, 0, sizeof(a));
}
Но в данном случае компилятор С++ не может вывести размер массива, и sizeof() выдаёт типичные 4 байта для 32-битной конфигурации. Тепение моё лопнуло и я полез в гугл: после 5 минут гугления выяснилось, что существует такое решение этой проблемы:
template < typename T, size_t N >
inline void zeroarray(T(&a)[N])
{
    return memset(a, 0, sizeof(T) * N);
}
Я не эксперт в С++, и мне сложно понять, что это за китайская грамота написана в виде параметра функции. Понимаю только одно - выглядит фигово :)

4 комментария:

  1. Запись вида inline void zeroarray(T a[N]) для компилятора тоже самое что и inline void zeroarray(T* a). Т.е. он не понимает разницы из-за наследния С. Где имя массива есть указатель на первый его элемент. Из-за этого систем типов С++ не может вывести что здесь происходит.

    Запись же ввиде inline void zeroarray(T(&a)[N])
    Говорит компилятору что эта функция принимает ссылку на какой-то тип, он пытается вывести и ты получаешь что твой тип есть T[N].

    Т.е. принципиальный момент во всем этом именно в разнице указатель на массив и ссылка на массив. И опять так из-за обратной совместимости с С.

    P.S. Более детально можешь почитать в книге "Шаблоны С++" Джоссатиса и Вандервуда.

    ОтветитьУдалить
  2. Вообще в этом месте С++ кривоват. Правда утверждать что T[N] - тип, немного странно. Но спасибо за разъяснение.

    ОтветитьУдалить
  3. Привет!
    Ну не сказал что это проблема С++ как такового, т.к. современный С++ (точнее если использовать его именно как С++ а не как расширение С) выглядит давольно стройно, если не пытаться залазить в его темные места, которые находятся прямо на границе стандарта.

    Насчет "утверждать что T[N] - тип, немного странно" оно кажется странным только до тех пор пока это выражение не переписать ввиде:
    typedef int TypeOfIntArrayOf10Elemets[10];
    После этой записи TypeOfIntArrayOf10Elemets с точки зрения типов С++ есть полноценный тип и его можно ипользовать также как или любой другой.
    Заголовок твоей функции в таком случае мог бы выглядеть следующим образом (к сожаление это не валидный код для текущего С++, это скорее псевдокод для понятности что происходит)
    typedef T ArrayOfNElements[N];
    inline void zeroarray(ArrayOfNElements& a)

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

    ОтветитьУдалить
  4. Про typedef int TypeOfIntArrayOf10Elemets[10] я действительно не знал. Спасибо за хинт.

    ОтветитьУдалить