• Метапрограммирование и метафункции
  • Частичная специализация по виду аргумента шаблона
  • Метафункция IsPointer‹T›

  • Симуляция частичной специализации по виду аргумента шаблона

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

    Метапрограммирование и метафункции

    Прежде чем перейти к изложению дальнейшего материала, полезно ввести понятия метапрограммирования и метафункции. Если внимательнее посмотреть на то, что происходит, когда компилятор встречает пример, подобный наследованию класса Matrix от MatrixTraits‹T›::…::Base, можно заметить, что фактически это является программированием компилятора. То есть, в данном случае компилятор как бы получает инструкцию: «если тип шаблона является типом float, то считать базовым классом Matrix_float_‹›, в противном случае – Matrix_‹›. Это можно рассматривать как программирование вычислений времени компиляции. Подобные техники иногда называют метапрограммированием шаблонами или просто метапрограммированием, а шаблоны, подобные MatrixTraits, – метафункциями.

    Частичная специализация по виду аргумента шаблона

    Одним из аспектов частичной специализации является возможность специализировать шаблон по виду аргумента, например, предоставить общую для всех указателей специализацию шаблона:

    template‹class T›

    class С {

     //…

    };


    template‹class T›

    class С‹T*› {

     //…

    };

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

    template‹class T›

    struct IsPointer {

     static const bool value =…;

    };

    где IsPointer‹T›::value принимает значения true или false в зависимости от того, является ли тип T указателем.

    ПРИМЕЧАНИЕ Так как некоторые компиляторы не поддерживают должным образом определение статических констант времени компиляции в теле класса, эта метафункция может быть переписана эквивалентным образом с использованием enum.

    Метафункция IsPointer‹T›

    Задачу построения подобной метафункции решили в 2000 году сотрудники Adobe Systems Incorporated Мэт Маркус и Джесс Джонс. Суть решения сводится к использованию выражения вызова перегруженных функций внутри sizeof():

    // Типы TrueType и FalseType могут быть определены произвольным образом,

    // главное чтобы выполнялось условие: sizeof(TrueType)!= sizeof(FalseType).

    struct TrueType {char dummy_ [1];};

    struct FalseType {char dummy_ [100];};


    // Промежуточный класс PointerShim нужен,

    // чтобы избежать ошибочной работы метафункции

    // IsPointer в случае параметризации классом, в котором определен

    // оператор преобразования к указателю.

    struct PointerShim {

     PointerShim(const volatile void*);

    };

    // Т.к. функции ptr_discriminator на самом деле не вызываются, реализации не требуется.

    TrueType ptr_discriminator(PointerShim);

    FalseType ptr_discriminator(…);


    // IsPointer‹T›::value == true, если T является указателем,

    // IsPointer‹T›::value == false в противном случае.

    template‹class T›

    class
    IsPointer {

    private:

     static T t_;

    public:

     enum {

     value = sizeof(ptr_discriminator(t_)) == sizeof(TrueType)};

    };


    // Так как объект типа void создан быть не может,

    // случай IsPointer‹void› должен обрабатываться отдельно.

    template‹›

    class IsPointer‹void› {

    public:

     enum {value = false};

    };

    ПРЕДУПРЕЖДЕНИЕ Строго говоря, необходимо предоставлять не только специализацию для void, но и для соответствующих cv-квалифицированных разновидностей: const void, volatile void, const volatile void. Эти специализации опущены для краткости изложения.

    ПРИМЕЧАНИЕ Функции, подобные ptr_discriminator, иногда называют дискриминирующими.

    Техника основана на том, что во время компиляции выражения sizeof(ptr_discriminator(t_)) компилятор вынужден выбрать из двух перегруженных функций ptr_discriminator наиболее подходящую. В случае, если IsPointer‹T›::t_ является указателем, будет выбрана функция ptr_discriminator(PointerShim), возвращающая значение типа TrueType, и значение IsPointer‹T›::value обращается в true, т.к. sizeof(ptr_discriminator(PointerShim)) – sizeof(TrueType); в противном случае подходящей является функция ptr_discriminator(…)и значением IsPointer‹T›::value является false, т.к. sizeof(ptr_discriminator(…)) – sizeof(FalseType), а типы TrueType и FalseType выбраны таким образом, что sizeof(TrueType)!= sizeof(FalseType).

    Класс PointerShim необходим для того, чтобы классы, имеющие операцию приведения к указателю, не считались указателями. На первый взгляд может показаться, что можно «упростить» дискриминирующие функции ptr_discriminator, избавившись от промежуточного класса PointerShim:

    TrueType simple_ptr_discriminator(const volatile void*);

    FalseType simple_ptr_discriminator(…);

    Однако, в этом случае, метафункция IsPointer будет работать неверно, например, для таких классов:

    struct C {

     operator int*() const {return 0;}

    };

    Так как класс C имеет операцию приведения к указателю, функция simple_ptr_discriminator может быть вызвана с любым объектом этого класса, и, следовательно, метафункция, построенная с использованием simple_ptr_discriminator, будет ошибочно определять подобные классы как указатели.

    Пример. Для пущей ясности можно рассмотреть, как работает метафункция IsPointer‹T› на примере типа int. IsPointer‹int› разворачивается компилятором примерно в следующее:

    // псевдокод

    class IsPointer‹int› {

    private:

     static int t_;

    public:

     enum {value = sizeof(ptr_discriminator(t_)) == sizeof(TrueType)};

    };

    ptr_discriminator(PointerShim) для t_ не подходит, т.к. объект PointerShim может быть создан только из указателя. Следовательно, подходящей будет оставшаяся ptr_discriminator(…), которая возвращает FalseType. Значит, в данном случае выражение sizeof(ptr_discriminator(t_)) эквивалентно выражению sizeof(FalseType), значение которого по условию не равно sizeof(TrueType). Следовательно, IsPointer‹int›::value == false.

    Симуляция частичной специализации по виду аргумента шаблона

    Использовать полученную метафункцию IsPointer‹T› для симуляции частичной специализации по виду аргумента шаблона можно примерно следующим образом:

    // Реализация общего случая: T не является указателем.

    template‹class T›

    class C_ {

     //…

    };


    // Реализация случая, когда T является указателем.

    template‹class T›

    class C_ptr_ {

     //…

    };


    // Traits для случая, когда T является указателем

    template‹bool T_is_ptr›

    struct CTraits {

     template‹class T›

     struct Args {

      typedef C_ptr_‹T› Base;

     };

    };


    // Traits для случая, когда T не является указателем.

    template‹›

    struct CTraits‹false› {

     template‹class T› struct Args {

      typedef C_‹T› Base;

     };

    };


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

    template‹class T›

    class C: public CTraits‹IsPointer‹T›::value›::template Args‹T›::Base {

     //…

    };







     

    Главная | В избранное | Наш E-MAIL | Добавить материал | Нашёл ошибку | Наверх