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

时间:2013-01-30 11:41:17

标签: c++ c++11 constructor sfinae

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

5 个答案:

答案 0 :(得分:27)

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

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

答案 1 :(得分: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);

答案 2 :(得分:7)

有许多方法可以触发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"

如果满足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>::valuefalseis_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,运算符会丢弃先前的表达式),只有当所有子表达式都是可编译的时。

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

答案 3 :(得分:3)

  

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

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

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

答案 4 :(得分: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,从而决定是否在过载解析中考虑此方法,如果require子句为true,则该方法将比另一种没有require子句的方法更可取,因为它更加专门。

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