C++ 可变参数模板使用 std::enable_if 进行部分模板特化

问题描述 投票:0回答:2

我的问题是如何在可变参数模板部分模板专业化场景中使用

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();
            }
        }

但是上面的代码不起作用,我的问题是如何像上面那样做?

c++ templates c++14 variadic-templates template-meta-programming
2个回答
3
投票

enable_if<bool b, T=void>
是一个类模板,定义
type=T
if
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
函数。如果你愿意的话我可以添加。


0
投票

解决问题的一种方法是添加一层间接:

template<typename Spec, typename...Ts>
struct foo_impl;

template <typename... Ts>
using foo = foo_impl<spec_t<Ts...>, Ts...>;

然后您可以使用

Spec
来区分整数和其他情况。弄清楚它是在
spec_t
中完成的:

template<typename... Ts>
struct spec
{
  using type = void;
};

template<typename H, typename... Ts>
struct spec<H, Ts...>
{
  using type =
      typename std::conditional<std::is_integral<H>::value, Int, void>::type;
};

template<typename... Ts>
using spec_t = typename spec<Ts...>::type;

其余部分与您最初建议的基本相同。这是完整的代码:

#include <iostream>
#include <type_traits>

struct Int;

/**
 * Spec is used to determine whether to use the integral case.
 */
template<typename... Ts>
struct spec
{
  using type = void;
};

template<typename H, typename... Ts>
struct spec<H, Ts...>
{
  using type =
      typename std::conditional<std::is_integral<H>::value, Int, void>::type;
};

template<typename... Ts>
using spec_t = typename spec<Ts...>::type;

/**
 *  Common case.
 */
template<typename Spec, typename...Ts>
struct foo_impl;

template <typename... Ts>
using foo = foo_impl<spec_t<Ts...>, Ts...>;

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

/**
 *  Regular foo_impl class.
 */
template <typename Spec, typename H, typename... Ts>
struct foo_impl<Spec, H, Ts...>
    : public foo<Ts...> {
  using super = foo<Ts...>;
  void func() {
    std::cout << "Hi" << std::endl;
    super::func();
  }
};

template <typename H, typename... Ts>
struct foo_impl<Int, H, Ts...>
    : public foo<Ts...> {
  using super = foo<Ts...>;
  void func() {
    std::cout << "Hooray Integral" << std::endl;
    super::func();
  }
};

int main()
{
    foo<int,int,char,double> x;
    x.func();
}
© www.soinside.com 2019 - 2024. All rights reserved.