30

在模板元编程中,可以在返回类型上使用 SFINAE来选择某个模板成员函数,即

template<int N> struct A {
  int sum() const noexcept
  { return _sum<N-1>(); }
private:
  int _data[N];
  template<int I> typename std::enable_if< I,int>::type _sum() const noexcept
  { return _sum<I-1>() + _data[I]; }
  template<int I> typename std::enable_if<!I,int>::type _sum() const noexcept
  { return _data[I]; }
};

但是,这不适用于构造函数。假设,我想声明构造函数

template<int N> struct A {
   /* ... */
   template<int otherN>
   explicit(A<otherN> const&); // only sensible if otherN >= N
};

但不允许它为otherN < N.

那么,这里可以使用 SFINAE 吗?我只对允许自动模板参数推导的解决方案感兴趣,这样

A<4> a4{};
A<5> a5{};
A<6> a6{a4};  // doesn't compile
A<3> a3{a5};  // compiles and automatically finds the correct constructor

注意:这是一个非常简化的示例,其中 SFINAE 可能是多余的,static_assert但可能就足够了。但是,我想知道是否可以改用 SFINAE。

4

5 回答 5

30

您可以向模板添加默认类型参数:

template <int otherN, typename = typename std::enable_if<otherN >= N>::type>
explicit A(A<otherN> const &);
于 2013-01-30T11:46:46.083 回答
17

触发 SFINAE 的方法有很多,这enable_if只是其中之一。首先:

Wats 是 std::enable_if 吗?

就是这样:

template<bool, class T=void> enable_if{ typedef T type; };
template<class T> enable_if<false,T> {};
template<bool b, class T=void> using enable_if_t = typename enable_f<b,T>::type;

这个想法是为了typename enable_if<false>::type成为一个错误,因此跳过任何包含它的模板声明。

那么这个触发功能怎么选呢?

禁用功能

这个想法在某些方面使声明错误:

按返回类型

template<class Type>
std::enable_if_t<cond<Type>::value,Return_type> function(Type);

通过实际参数

template<class Type>
return_type function(Type param, std::enable_if_t<cond<Type>::value,int> =0) 

通过模板参数

template<class Type, 
    std::enable_if_t<cond<Type>::value,int> =0> //note the space between > and =
return_type function(Type param) 

选择功能

您可以使用以下技巧参数化不同的替代方案:

tempplate<int N> struct ord: ord<N-1>{};
struct ord<0> {};

template<class T, std::enable_if<condition3, int> =0>
retval func(ord<3>, T param) { ... }

template<class T, std::enable_if<condition2, int> =0>
retval func(ord<2>, T param) { ... }

template<class T, std::enable_if<condition1, int> =0>
retval func(ord<1>, T param) { ... }

template<class T> // default one
retval func(ord<0>, T param) { ... }

// THIS WILL BE THE FUCNTION YOU'LL CALL
template<class T>
retval func(T param) { return func(ord<9>{},param); } //any "more than 3 value"

如果满足,这将调用第一个/第二个/第三个/第四个函数,而不是没有一个。condition3condition2condition1

其他 SFINAE 触发器

编写编译时条件可以是显式特化的问题,也可以是未评估的表达式成功/失败的问题:

例如:

template<class T, class = void>
struct is_vector: std::false_type {};
template<class X>
struct is_vector<vector<X> >:: std::true_type {};

所以那is_vector<int>::valuefalse但是is_vecttor<vector<int> >::valuetrue

或者,通过自省,比如

template<class T>
struct is_container<class T, class = void>: std::false_type {};

template<class T>
struct is_container<T, decltype(
  std::begin(std::declval<T>()),
  std::end(std::declval<T>()),
  std::size(std::declval<T>()),
  void(0))>: std::true_type {};

所以如果给定,你is_container<X>::value可以编译等。trueX xstd::begin(x)

诀窍是只有当所有子表达式都是可编译的时,decltype(...)实际上才是void(运算符丢弃先前的表达式)。,


甚至可以有许多其他选择。希望在这一切之间你能找到一些有用的东西。

于 2018-05-28T08:33:44.650 回答
8

在 C++11 中,您可以使用默认模板参数:

template <int otherN, class = typename std::enable_if<otherN >= N>::type>
explicit A(A<otherN> const &);

但是,如果您的编译器还不支持默认模板参数,或者您需要多个重载,那么您可以使用这样的默认函数参数:

template <int otherN>
explicit A(A<otherN> const &, typename std::enable_if<otherN >= N>::type* = 0);
于 2013-01-30T23:09:15.013 回答
8

在大多数情况下,公认的答案是好的,但如果存在两个具有不同条件的构造函数重载,则会失败。我也在寻找这种情况下的解决方案。

是:接受的解决方案有效,但不适用于两个替代构造函数,例如,

template <int otherN, typename = typename std::enable_if<otherN == 1>::type>
explicit A(A<otherN> const &);

template <int otherN, typename = typename std::enable_if<otherN != 1>::type>
explicit A(A<otherN> const &);

因为,如本页所述,

一个常见的错误是声明两个仅在默认模板参数上有所不同的函数模板。这是非法的,因为默认模板参数不是函数模板签名的一部分,并且用相同的签名声明两个不同的函数模板是非法的。

正如在同一页面中提出的那样,您可以应用 SFINAE 解决此问题,修改签名,到值(不是类型)模板参数的类型,如下所示

template <int otherN, typename std::enable_if<otherN == 1, bool>::type = true>
explicit A(A<otherN> const &);

template <int otherN, typename std::enable_if<otherN != 1, bool>::type = true>
explicit A(A<otherN> const &);
于 2019-04-06T21:19:59.017 回答
1

使用 C++20,您可以使用requires关键字

使用 C++20,您可以摆脱 SFINAE。

requires关键字是enable_if!的简单替代品

请注意,otherN == N 的情况是一种特殊情况,因为它属于默认副本 ctor,所以如果你想处理它,你必须单独实现它:

template<int N> struct A {
   A() {}    

   // handle the case of otherN == N with copy ctor
   explicit A(A<N> const& other) { /* ... */ }

   // handle the case of otherN > N, see the requires below
   template<int otherN> requires (otherN > N)
   explicit A(A<otherN> const& other) { /* ... */ }

   // handle the case of otherN < N, can add requires or not
   template<int otherN>
   explicit A(A<otherN> const& other) { /* ... */ }
};

requires子句得到一个constant expression评估truefalse决定是否在重载决议中考虑此方法,如果 requires 子句为真,则该方法优于另一个没有 requires 子句的方法,因为它更专业。

代码:https ://godbolt.org/z/RD6pcE

于 2020-04-09T20:27:11.863 回答