Частичная специализация шаблонов вариабельных шаблонов C ++ с помощью std :: enable_if - PullRequest
4 голосов
/ 13 мая 2019

ITNOA

Мой вопрос: как использовать std::enable_if в сценарии частичной специализации шаблона с вариационным шаблоном?

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

        /**
         *  Common case.
         */
        template<typename... Args>
        struct foo;

        /**
         *  Final superclass for foo.
         */
        template<>
        struct foo<>{ void func() {} };

        /**
         *  Regular foo class.
         */
        template<typename H, typename... T>
        struct foo<H, T...> : public foo<T...> {
            typedef super foo<T...>;
            void func() {
                cout << "Hi" << endl;
                this->super::template func();
            }
        }

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

        enum class enabled {_};
        template<typename H, typename... T>
        struct foo<H, typename std::enable_if<std::integral_type<H>::value,enabled>::type = enabled::_, T...> : public foo<T...> {
            typedef super foo<T...>;
            void func() {
                cout << "Hooray Inegral" << endl;
                this->super::template func();
            }
        }

но приведенный выше код не работает, мой вопрос, как это сделать, как указано выше?

1 Ответ

2 голосов
/ 13 мая 2019

enable_if<bool b, T=void> - это шаблон класса, который определяет type=T, если b==true.Таким образом, enable_if<b>::type является допустимым выражением, только если b==true.SFINAE заявляет, что ошибка замены не является ошибкой.ИМХО not_disable_if будет более подходящим именем.

Частичная специализация работает путем сопоставления шаблонов с текущим разрешенным типом.Вы не можете добавить новые аргументы шаблона в него, потому что он будет соответствовать чему-то другому.struct foo<H,std::enable_if_t<...,void>> соответствует foo<H,void>, только если ... вычитается из H и оценивается в true.

struct foo<std::enable_if_t<std::is_integral_v<H>,H>> не может работать просто потому, что нет способа вывести H из, например, foo<int>.Он должен был бы каким-то образом вывести семантику enable_if и is_integral и увидеть, что если H=int, то он разрешается точно в foo<int>, что, конечно, не может быть сделано в общем.

SFINAE может толькоприменяться только к тем частям, которые рассматриваются при разрешении перегрузки.Первый и тот, который вы использовали, это аргументы шаблона класса, но, как я уже говорил выше, это изменит то, что на самом деле соответствует.Другой вариант - сами параметры шаблона.Но C ++ запрещает аргументы шаблона по умолчанию для специализации классов, которые обычно используются для этого.Там нет возвращаемого значения, как в функциях.SFINAE не использует ничего внутри тела класса или его базовых классов.Я не думаю, что вам нужна текущая настройка.

Я предложу небольшой редизайн:

#include <iostream>
#include <type_traits>

// Only specifies behaviour for the head part - one T
// Ts... can be ignored, but are required.
//  - I left them because I'm not sure whether you want to use them in the real code.
//  - But they are required because `foos` use them as base classes and they must be unique.
template<typename T,bool is_integral,typename...Ts>
struct foo_impl;

template<typename T,typename...Ts>
struct foo_impl<T,true,Ts...>
{
    void func() {
        std::cout << "Hooray Inegral" << std::endl;
    }
};

template<typename T,typename...Ts>
struct foo_impl<T,false,Ts...>
{
    void func() {
        std::cout << "Hi" << std::endl;
    }
};

template<typename T,typename...Ts>
using foo = foo_impl<T,std::is_integral<T>::value,Ts...>;

template<typename...Ts>
struct foos;

template<typename H,typename...Ts>
struct foos<H,Ts...> : private foo<H,Ts...>, public foos<Ts...>
{
   using head = foo<H,Ts...>;
   using tail = foos<Ts...>;
   //Only named differently for clarity, feel free to rename it back to 'func'
   void rec_func()
   {
       //If we inherited from foo<H> so this was only this->foo<H>::func() then
       //foos<int,int> would have two foo<int> base classes and this call would be ambigious.
       this->head::func();
       this->tail::rec_func();
   }
};

template<> struct foos<>{
    void rec_func(){}
};

int main()
{
    foos<int,int,char,double> x;
    x.rec_func();
}

* foo имеет дело только с одним T иимеет необходимые специализации.Вы можете извлечь любое общее поведение между foo<T,false> и foo<T,true> в общий базовый класс.В настоящее время существует дублирование между foo и foos API.Но foo 'API может рассматриваться как частный и может отличаться, поэтому я бы не сказал, что это вообще недостаток.Как я объяснил Ts... в foo_impl требуется.Если они вам не нужны, они могут быть удалены, например, путем простого извлечения из std::tuple<foo<Ts>...> и некоторых выражений свертывания (C ++ 17) / magic (c ++ 14) для вызова функций func.Я могу добавить это, если хотите.

...