Шаблон typedef? - PullRequest
       44

Шаблон typedef?

2 голосов
/ 27 января 2020

Во внешнем API я определил структуры: Foo1, Foo4, Foo8, Foo16

Теперь мне нужно определить четыре функции:

void bar(Foo1*);
void bar(Foo4*);
void bar(Foo8*);
void bar(Foo16*);

Эти функции делают то же самое в oop итерации 1, 4, 8 и 16 раз.

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

template<unsigned int N> void bar(Foo<N> * foo)
{
    for(unsigned int i=0;i<N;++i)
    {
        //Some critical code that optimizes nicely with SSE, AVX, etc...
    }
}

Но я не знаю, как определить класс шаблона Foo<N>, чтобы он специализировался на Foo1, Foo4, Foo8, Foo16

Возможно ли это?

Я знаю, что мог бы создать структуру шаблона:

template<unsigned int N> struct Foo;
template<> struct Foo<1>{ Foo1 f; };
template<> struct Foo<4>{ Foo4 f; };
template<> struct Foo<8>{ Foo8 f; };
template<> struct Foo<16>{ Foo16 f; };

Это было бы функционально идентично тому, чего я хочу достичь, но несколько расширяет код bar, который будет полон foo.f с и зависит от приведения от FooN* до Foo<N>*.

Ответы [ 5 ]

2 голосов
/ 27 января 2020

Для запуска l oop в зависимости от класса вы можете использовать константное выражение, начинающееся с C ++ 11:

template<typename T>
void bar(T) {
    constexpr unsigned int N = std::is_same<T, Foo4>::value * 4 + std::is_same<T, Foo16>::value * 16; // likewise for Foo1, Foo8
    // ...
}

Выражение вычисляется во время компиляции, поэтому N известно в время компиляции. Если метод вызывается с не охваченным классом, в этом экземпляре метода N равно 0. Вы можете использовать static_assert, чтобы проверить это во время компиляции:

static_assert(N > 0, "bar() called with object of invalid type");

все вместе для четырех классов, это будет выглядеть так:

template<typename T>
void bar(T *a) {
    constexpr unsigned int N = std::is_same<T, Foo1>::value * 1
        + std::is_same<T, Foo4>::value * 4
        + std::is_same<T, Foo8>::value * 8
        + std::is_same<T, Foo16>::value * 16;
    static_assert(N > 0, "bar() called with object of invalid type");

    for( unsigned int i = 0; i < N; ++i) {
        std::cout << i << std::endl;
    }
}

Вот и все!

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

#include <type_traits>

template<typename T>
typename std::enable_if_t<(std::is_same<T, Foo4>::value || std::is_same<T, Foo16>::value)>
bar(T *a) {
    // code
}

При этом используется тип возвращаемого значения, чтобы функция компилировалась только в том случае, если создается с допустимым типом (Foo4 или Foo16). Начиная с C ++ 17, вы также можете использовать std::disjunction.

2 голосов
/ 27 января 2020

Как насчет этого? Специализируйте функцию, а не сам тип:

template<typename FooT, unsigned N>
void bar_impl(FooT f)
{
    for (unsigned int i = 0; i < N; ++i)
    {
        //Do magic!
    }
}

template<typename FooT>
void bar(FooT f);

template<>
void bar<Foo1>(Foo1 f)
{
    bar_impl<Foo1, 1>(f);
}
template<>
void bar<Foo4>(Foo4 f)
{
    bar_impl<Foo4, 4>(f);
}
template<>
void bar<Foo8>(Foo8 f)
{
    bar_impl<Foo8, 8>(f);
}
template<>
void bar<Foo16>(Foo16 f)
{
    bar_impl<Foo16, 16>(f);
}
2 голосов
/ 27 января 2020

Я не знаю, как определить класс шаблона Foo, чтобы он специализировался на Foo1, Foo4, Foo8, Foo16

Примерно так:

template <int N> struct Foo_impl {};
template <> struct Foo_impl<1 > {using type = Foo1 ;};
template <> struct Foo_impl<4 > {using type = Foo4 ;};
template <> struct Foo_impl<8 > {using type = Foo8 ;};
template <> struct Foo_impl<16> {using type = Foo16;};
template <int N> using Foo = typename Foo_impl<N>::type;

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

template <int N> void bar(Foo<N> *foo) {}

int main()
{
    Foo<4> x;
    bar(&x); // error: no matching function for call to 'bar'
             // note: candidate template ignored: couldn't infer template argument 'N'
}

Чтобы заставить его работать, вы должны использовать что-то вроде template <typename T> void bar(T *foo) {}, с static_assert (или какой-то другой прием) ограничивает T одним из этих 4 типов.

Вы можете сделать что-то вроде этого:

template <typename T> void bar(T *foo)
{
    constexpr int N =
        std::is_same_v<T, Foo1 > ? 1  :
        std::is_same_v<T, Foo4 > ? 4  :
        std::is_same_v<T, Foo8 > ? 8  :
        std::is_same_v<T, Foo16> ? 16 : throw "Invalid T.";
    // ...
}

Здесь throw "Invalid T." на самом деле не выбрасывает во время выполнения , но вызывает ошибку времени компиляции, если T не равно Foo#.

1 голос
/ 27 января 2020

Вы можете использовать вспомогательную структуру bar_trait, которую вы специализируете для каждого типа Foo1, Foo4,…. Затем эти специализации могут содержать различные значения, которые затем можно использовать в функции bar для управления потоком кода.

Таким образом bar может принимать любой тип, для которого вы создаете специализацию bar_trait и поддерживает выражение, примененное к нему в bar.

Преимущества этого:

  • можно оценить во время компиляции, и у вас не будет ошибок времени выполнения
  • вы можете специализироваться bar_trait, не касаясь bar
  • , вы можете повторно использовать bar_trait в других местах, где может потребоваться размер FooN, поэтому меньше повторений кода.
  • нет требований к Маркосу
  • шаблонные аргументы для вывода аргументов
struct Foo1 {};
struct Foo4 {};
struct Foo8 {};
struct Foo16 {};

// create a declaration for bar_trait but no definition so that specializations are required
template<typename T>
struct bar_trait;

// create the specializations for `Foo1`, `Foo4`, …
template<>
struct bar_trait<Foo1> {
    static constexpr const size_t size = 1;
};

template<>
struct bar_trait<Foo4> {
    static constexpr const size_t size = 4;
};

template<>
struct bar_trait<Foo8> {
    static constexpr const size_t size = 8;
};

template<>
struct bar_trait<Foo16> {
    static constexpr const size_t size = 16;
};

// accepts any type for T as long as a specialization for `bar_trait<T>` exists
// and the expressions applied on `foo` are valid
template<typename T> void bar(T * foo)
{
    constexpr const auto N = bar_trait<T>::size;

    for(unsigned int i=0;i<N;++i)
    {
        //Some critical code that optimizes nicely with SSE, AVX, etc...
    }
}

int main()
{
   Foo1 foo1;
   Foo4 foo4;

   bar(&foo1);
   bar(&foo4);
}
0 голосов
/ 27 января 2020

Вы можете объявить следующий шаблон класса удобства, Foo_number_taker:

template<typename> struct Foo_number_taker;

Затем, с помощью следующего макроса, DEF_FOO_NUMBER_TAKER:

#define DEF_FOO_NUMBER_TAKER(N) template<> \
                                struct Foo_number_taker<Foo##N> { \
                                   static constexpr unsigned value = N; \
                                }; 

Вы можете легко специализировать шаблон класса Foo_number_taker выше для Foo1, Foo4, Foo8 и Foo16:

DEF_FOO_NUMBER_TAKER(1)
DEF_FOO_NUMBER_TAKER(4)
DEF_FOO_NUMBER_TAKER(8)
DEF_FOO_NUMBER_TAKER(16)

Например, DEF_FOO_NUMBER_TAKER(8) будет расширяться до:

template<> struct Foo_number_taker<Foo8> { static constexpr unsigned value = 8; };

Следовательно, он каким-то образом ассоциирует тип Foo8 с числом 8 , а макрос предотвращает ошибочные ассоциации (например, ассоциирование Foo8 с числом 4 ) от происходящего. Аналогично для остальных специализаций.

При желании вы можете просто определить следующий шаблон переменной:

template<typename FooN>
static auto constexpr Foo_number_v = Foo_number_taker<FooN>::value;

Наконец, вы определяете свой шаблон функции bar(), чтобы воспользоваться преимуществами этого шаблона переменной выше, чтобы выяснить номер, связанный с параметром шаблона FooN:

template<typename FooN>
void bar(FooN* foo) {
   auto constexpr N = Foo_number_v<FooN>;

   for(unsigned i = 0; i < N; ++i) {
      // ...
   }
}

Использовать его просто:

auto main() -> int {
   Foo1 *f1;
   Foo4 *f4;

   // ...

   bar(f1);
   bar(f4);
}
...