模板功能只匹配某些类型?

时间:2015-07-23 19:35:02

标签: c++ templates typetraits

我想定义一个功能模板:

T

但我希望T仅匹配某些类型。具体来说,{{1}}应该派生(可能通过多重继承)形成某个基类。否则,此模板不应包含在重载集中。

我该怎么做?

3 个答案:

答案 0 :(得分:13)

将SFINAE与std::is_base_of

一起使用
template <typename T,
          typename = std::enable_if_t<
              std::is_base_of<Foo, T>::value
          >>
void foo(T arg);

如果foo继承自T,则只会在重载集中包含Foo。请注意,这也包括模糊和不可访问的基础。如果您想要的解决方案只允许TFoo公开继承 ,那么您可以使用std::is_convertible }:

template <typename T,
          typename = std::enable_if_t<
              std::is_convertible<T*, Foo*>::value
          >>
void foo(T arg);

请注意参数的反转。

无论选择哪种形式,为简洁起见都可以使用别名:

template <typename T>
using enable_if_foo = std::enable_if_t<std::is_base_of<Foo, T>::value>;

template <typename T,
          typename = enable_if_foo<T>>
void foo(T arg);

这是有效的,因为std::enable_if有一个名为type的嵌套类型,当且仅当传入的布尔值为true时。因此,如果std::is_base_of<Foo, T>::valuetrue,则enable_if_t会被实例化为void,就好像我们写过:

template <typename T,
          typename = void>
void foo(T arg);

但是,如果T未从Foo继承,那么类型特征将评估为falsestd::enable_if_t<false>是替换失败 - 没有{{ 1}}。你可能会认为这是一个编译错误,但 s ubstitution f ailure i s n ot a n e rror(sfinae)。它只是模板演绎失败。因此,效果是在这种情况下,typename enable_if<false>::type只是从可行的重载候选集中删除,与任何其他模板推导失败没有区别。

答案 1 :(得分:5)

基于SFINAE的技术,如下所示;

template <typename T,
  typename Test = std::enable_if_t<std::is_base_of<Foo, T>::value>>
void foo(T arg);

最好从过载列表中删除该功能 - 这是一般情况。

如果您希望将该函数保留在列表中,并且如果类型符合某些条件(例如此处的基本要求),如果选择作为失败的最佳重载,则可以使用static_assert;

template <typename T>
void foo(T arg)
{
  static_assert(std::is_base_of<Foo, T>::value, "failed type check");
  // ...
}

答案 2 :(得分:4)

在带有concepts lite的C ++ 1z中,您可以这样做:

template<class T>
requires std::is_base_of<Foo, T>{}()
void foo(T arg) {
}

在当前(实验)实施下。这很干净清晰。 可能是一种做某事的方法:

template<derived_from<Foo> T>
void foo(T arg) {
}

但我还没有解决。你绝对可以做到:

template<derived_from_foo T>
void foo(T arg){
}

我们有一个名为derived_from_foo的自定义概念,如果类型派生自foo,则适用。我不知道怎么做的是模板概念 - 从模板类型参数生成的概念。

在C ++ 14中,这里有两种方法。首先,正常的SFINAE:

template<class T,
  class=std::enable_if_t<std::is_base_of<Foo, T>{}>
>
void foo(T arg) {
}

这里我们创建一个模板,从其参数中推导出类型T。然后它尝试从第一个参数推断出第二个类型参数。

第二个类型参数没有名称(因此class=),因为我们只将它用于SFINAE测试。

测试是enable_if_t< condition >。如果enable_if_t< condition >为真,则void会生成condition类型。如果condition为false,则在“直接上下文”中失败,从而导致替换失败。

SFINAE是“替换失败不是错误” - 如果类型T在函数模板签名的“直接上下文”中生成失败,则不会生成编译时错误,但相反,导致函数模板在这种情况下不被视为有效的重载。

“直接上下文”在这里是一个技术术语,但基本上它意味着错误必须“足够早”才能被捕获。如果需要编译函数体来查找错误,那就不在“直接上下文”中。

现在,这不是唯一的方法。我个人喜欢将我的SFINAE代码隐藏在尊重的光泽之下。下面,我使用标签调度来“隐藏”其他地方的故障,而不是在功能签名中将其放在前面:

template<class T>
struct tag {
  using type=T;
  constexpr tag(tag const&) = default;
  constexpr tag() = default;
  template<class U,
    class=std::enable_if_t<std::is_base_of<T,U>{}>
  >
  constexpr tag(tag<U>) {}
};

struct Base{};
struct Derived:Base{};

template<class T>
void foo( T t, tag<Base> = tag<T>{} ) {
}

这里我们创建一个tag调度类型,它允许转换为base。 tag让我们值得使用类型作为值,并对它们使用更常规的C ++操作(而不是遍布模板的元编程<>)。

然后我们给foo第二个tag<Base>类型的参数,然后用tag<T>构造它。如果T不是来自Base的派生类型,则无法编译。

live example

此解决方案的优点在于,使其无效的代码看起来更直观 - tag<Unrelated>无法转换为tag<Base>。但是,这并不妨碍将函数考虑用于重载解析,这可能是一个问题。

锅炉板较少的方法是:

template<class T>
void foo( T t, Base*=(T*)0 ) {
}

我们使用指针可以转换的事实,如果指针之间存在派生关系。

在C ++ 11中(没有constexpr支持),我们首先编写一个帮助器:

namespace notstd {
  template<bool b, class T=void>
  using enable_if_t=typename std::enable_if<b,T>::type;
}

然后:

template<class T,
  class=notstd::enable_if_t<std::is_base_of<Foo, T>::value>
>
void foo(T arg) {
}

如果你不喜欢帮助者,我们会得到这个丑陋的额外内容:

template<class T,
  class=typename std::enable_if<std::is_base_of<Foo, T>::value>::type
>
void foo(T arg) {
}

上面的第二个C ++ 14技术也可以翻译成C ++ 11。

如果需要,您可以编写一个执行测试的别名:

template<class U>
using base_test=notstd::enable_if_t<std::is_base_of<Base, U>::value>;

template<class T,
  class=base_test<T>
>
void foo(T arg) {
}