如何使用sfinae选择构造函数?

问题描述 投票:23回答:4

在模板元编程中,可以在返回类型上使用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。

c++ c++11 constructor sfinae
4个回答
27
投票

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

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

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

7
投票

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

什么是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成为一个错误,因此任何包含它的模板声明都会被跳过。

那么如何触发功能选择呢?

禁用功能

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

By return type

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

By a actual parameter

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

By a template parameter

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"

如果condition3满足,这将调用第一/第二/第三/第四函数,而不是condition2而不是condition1

其他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>::valuetrue将是X x,你可以编译std::begin(x)等。

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


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


4
投票

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

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

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 &);

因为,如this page所述,

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

正如在同一页面中提出的那样,您可以解决这个问题,将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 &);
© www.soinside.com 2019 - 2024. All rights reserved.