在模板元编程中,可以在返回类型上使用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。
答案 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
只是其中之一。
首先:
就是这样:
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
。
编写编译时条件可以是显式特化问题,也可以是未评估表达式成功/失败的问题:
例如:
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>::value
为false
但is_vecttor<vector<int> >::value
为true
或者,通过内省,如
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
,true
将为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)
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
,其求值为true
或false
,从而决定是否在过载解析中考虑此方法,如果require子句为true,则该方法将比另一种没有require子句的方法更可取,因为它更加专门。