Элегантная специализация шаблона

Есть ли элегантный способ специализировать шаблон на основе одного из параметров шаблона?

Ie.

template<int N> struct Junk {
    static int foo() {
        // stuff
        return Junk<N - 1>::foo();
    }
};

// compile error: template argument '(size * 5)' involves template parameter(s)
template<int N> struct Junk<N*5> {
    static int foo() {
        // stuff
        return N;
    }
};

template<> struct Junk<0> {
    static int foo() {
        // stuff
        return 0;
    }
};

Т.е. Я пытаюсь специализировать шаблон на основе параметра, который делится на 5. Единственный способ, которым я могу это сделать, выглядит следующим образом:

template<int N> struct JunkDivisibleBy5 {
    static int foo() {
        // stuff
        return N;
    }
};

template<int N> struct Junk {
    static int foo() {
        // stuff
        if ((N - 1) % 5 == 0 && N != 1)
            return JunkDivisibleBy5<N - 1>::foo();
        else
            return Junk<N - 1>::foo();
    }
};


template<> struct Junk<0> {
    static int foo() {
        // stuff
        return 0;
    }
};

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


person Greg Rogers    schedule 06.11.2008    source источник


Ответы (6)


Как это:

#include <iostream>
using namespace std;

template < typename T, T N, T D >
struct fraction {
    typedef T value_type;
    static const value_type num = N;
    static const value_type denom = D;
    static const bool is_div = (num % denom == 0);
};

template< typename T, T N, T D, bool P >
struct do_if {
    static void op() { cout << N << " NOT divisible by " << D << endl; }
};

template< typename T, T N, T D >
struct do_if< T, N, D, true > {
    static void op() { cout << N << " divisible by " << D << endl; }
};

template < int N >
void foo() {
    typedef fraction< int, N, 5 > f;
    do_if< typename f::value_type, f::num, f::denom, f::is_div >::op();
}

int main() {
    foo< -5 >();
    foo< -1 >();
    foo< 0 >();
    foo< 1 >();
    foo< 5 >();
    foo< 10000005 >();
    return 0;
}
person jwfearn    schedule 06.11.2008
comment
Я скомпилировал его, запустил и проверил результаты теста. Я включил всю программу, чтобы другие могли сделать то же самое. - person jwfearn; 06.11.2008
comment
не с g++ 4.1.3 meta_programming.cpp:10: ошибка: явная специализация в структуре области, не относящейся к пространству имен, my‹N› meta_programming.cpp:10: ошибка: шаблоны вложенных классов не являются явно специализированными - person David Nehme; 06.11.2008
comment
ПОЯСНЕНИЕ: я скомпилировал его с помощью Visual C++ 2008, запустил и проверил результаты теста. - person jwfearn; 06.11.2008
comment
Извините, @David Nehme, я не использую g++. Если у вас есть решение, совместимое с g++, опубликуйте его, потому что я хотел бы узнать больше о том, что я делаю неправильно. - person jwfearn; 06.11.2008
comment
Я переработал это, чтобы (надеюсь) быть более приемлемым для g++. Он по-прежнему отлично работает с VC9. @David Nehme, можете ли вы сообщить об успехе/неуспехе на g++? - person jwfearn; 06.11.2008
comment
Я пробовал с g++ 4.3.3: без кубиков :( ~$ g++ metaprog.cpp -o metaprog metaprog.cpp: в функции 'void foo()': metaprog.cpp:25: ошибка: несоответствие типа/значения в аргументе 1 в списке параметров шаблона для 'template‹class T, TN, TD, bool P› struct do_if' metaprog.cpp:25: ошибка: ожидался тип, получено 'f::value_type' metaprog.cpp:25: ошибка: '‹ ошибка типа›» не является допустимым типом для параметра константы шаблона metaprog.cpp:25: ошибка: «‹ошибка типа›» не является допустимым типом для параметра константы шаблона metaprog.cpp:25: ошибка: недопустимый тип в объявлении перед токеном ';' - person Matt J; 31.05.2009
comment
поместите имя типа перед f::value_type. f::value_type — это зависимый тип. - person Johannes Schaub - litb; 31.05.2009
comment
@MattJ, я отредактировал в соответствии с предложением @litb (я также сократил «числитель» до «число» и «знаменатель» до «деном», чтобы избежать горизонтальной полосы прокрутки). Это помогает? - person jwfearn; 31.05.2009

Используя шаблоны языка программирования D, можно было бы написать это так:

struct Junk(int N)
{
    static int foo()
    {
        static if (N == 0)
            return 0;
        else static if ((N % 5) == 0)
            return N;
        else
            return Junk!(N - 1).foo();
    }
}

статические операторы if выполняются во время компиляции.

person Walter Bright    schedule 11.11.2008
comment
Я не должен голосовать за это, но я сделал это, потому что это внезапно заставляет меня хотеть выучить D :) - person Mark K Cowan; 10.09.2015

Все расчеты могут быть сделаны во время компиляции:

#include <iostream>

template<int N> struct Junk {
    enum { IsDivisibleBy5 = (N % 5 == 0) };
    template<bool D> struct JunkInternal {
        enum { Result = Junk<N-1>::Result };
    };
    template<> struct JunkInternal<true> {
        enum { Result = N };
    };
    enum { Result = JunkInternal<IsDivisibleBy5>::Result };
};

int main(int, char**)
{
    std::cout << Junk< 0 >::Result << std::endl;
    std::cout << Junk< 7 >::Result << std::endl;
    std::cout << Junk< 10 >::Result << std::endl;

    return 0;
}
person Igor Semenov    schedule 06.11.2008

Код

template<int A, bool = !(A % 5)>
struct select : select<A-1> { };

template<int A>
struct select<A, true> { static int const value = A; };

template<>
struct select<0, true> { static int const value = 0; };

int main() {
    std::cout << select<1>::value; // 0
    std::cout << select<7>::value; // 5
    std::cout << select<10>::value; // 10
}

Сохранить переменную делителя

template<int A, int D, bool = !(A % D)>
struct select : select<A-1, D> { };

template<int A, int D>
struct select<A, D, true> { static int const value = A; };

template<int D>
struct select<0, D, true> { static int const value = 0; };

int main() {
    std::cout << select<1, 3>::value; // 0
    std::cout << select<7, 3>::value; // 6
    std::cout << select<10, 3>::value; // 9
}
person Johannes Schaub - litb    schedule 08.11.2008

Наследование работает довольно хорошо:

template<int N> struct Junk : private JunkBase < N % 5 > { };

template<int N> struct JunkBase {
    static int foo() {
        // stuff
        return Junk<N - 1>::foo();
    }
};
template< > struct JunkBase<0> {
    static int foo() {
        return 0;
    }
};

Вам может понадобиться передать N в JunkBase::foo, если вам тоже нужно N/5.

person MSalters    schedule 06.11.2008

Я бы вряд ли назвал это элегантным, но вот моя версия вашего кода, использующая только шаблоны для вычислений (вместе с тестовой штукой) -

#include <iostream>

template < int N > struct JunkDivBy5 {
    static int foo() {
        return N;
    }
};

template < int N > struct Junk {
    template < int N1 > struct _JunkCond {
        enum { val = ( N1 != 1 && ( N1 - 1 ) % 5 == 0 ) ? 1 : 0 };
    };

    template < int M, int N1 > struct _JunkBranch { /* Error */ };

    template < int N1 > struct _JunkBranch< 1, N1 > {
        typedef JunkDivBy5< N1 - 1 > Type;
    };

    template < int N1 > struct _JunkBranch< 0, N1 > {
        typedef Junk< N1 - 1 > Type;
    };

    static int foo() {
        return _JunkBranch< _JunkCond< N >::val, N >::Type::foo();
    }
};

template <> struct Junk< 0 > {
    static int foo() {
        return 0;
    }
};

int main( int argc, char *argv[] ) {
    std::cout << Junk< 0 >::foo() << std::endl;
    std::cout << Junk< 5 >::foo() << std::endl;
    std::cout << Junk< 7 >::foo() << std::endl;
    std::cout << Junk< 25 >::foo() << std::endl;
}
person hark    schedule 06.11.2008