C++ SFINAE的例子?

rlb*_*ond 113 c++ templates metaprogramming sfinae

我想进入更多的模板元编程.我知道SFINAE代表"替换失败不是错误".但是有人能告诉我SFINAE的用处吗?

Joh*_*itb 87

我喜欢SFINAE用来检查布尔条件.

template<int I> void div(char(*)[I % 2 == 0] = 0) {
    /* this is taken when I is even */
}

template<int I> void div(char(*)[I % 2 == 1] = 0) {
    /* this is taken when I is odd */
}
Run Code Online (Sandbox Code Playgroud)

它非常有用.例如,我用它来检查使用运算符逗号收集的初始化列表是否不长于固定大小

template<int N>
struct Vector {
    template<int M> 
    Vector(MyInitList<M> const& i, char(*)[M <= N] = 0) { /* ... */ }
}
Run Code Online (Sandbox Code Playgroud)

仅当M小于N时才接受该列表,这意味着初始化列表没有太多元素.

语法char(*)[C]表示:指向元素类型为char和size的数组的指针C.如果C为false(这里为0),那么我们得到无效类型char(*)[0],指向零大小数组的指针:SFINAE使得模板将被忽略.

用表示boost::enable_if,看起来像这样

template<int N>
struct Vector {
    template<int M> 
    Vector(MyInitList<M> const& i, 
           typename enable_if_c<(M <= N)>::type* = 0) { /* ... */ }
}
Run Code Online (Sandbox Code Playgroud)

在实践中,我经常发现能够检查条件是否有用.

  • @Johannes 奇怪的是,GCC (4.8) 和 Clang (3.2) 接受声明大小为 0 的数组(因此该类型并不是真正的“无效”),但它在您的代码上表现正常。在 SFINAE 与“常规”使用类型的情况下,可能对这种情况有特殊支持。 (2认同)
  • @v.oddou 试试“int foo[0]”。我对它的支持并不感到惊讶,因为它允许非常有用的“以 0 长度数组结尾的结构”技巧(https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html)。 (2认同)
  • @v.oddou 不,我的意思是 C++,实际上是 C++11:clang++ 和 g++ 都接受它,我已经指出了一个解释为什么这很有用的页面。 (2认同)

Gre*_*ers 67

下面是一个例子(从这里开始):

template<typename T>
class IsClassT {
  private:
    typedef char One;
    typedef struct { char a[2]; } Two;
    template<typename C> static One test(int C::*);
    // Will be chosen if T is anything except a class.
    template<typename C> static Two test(...);
  public:
    enum { Yes = sizeof(IsClassT<T>::test<T>(0)) == 1 };
    enum { No = !Yes };
};
Run Code Online (Sandbox Code Playgroud)

IsClassT<int>::Yes计算时,0无法转换为int int::*因为int不是类,所以它不能有成员指针.如果SFINAE不存在,那么你会得到一个编译器错误,类似'0无法转换为非类型int的成员指针'.相反,它只使用...返回Two 的表单,因此计算结果为false,int不是类类型.

  • 这里更奇怪的东西IMO不是`...`,而是`int C ::*`,我从未见过,不得不去查找.找到了答案,以及它可能用于何处:http://stackoverflow.com/questions/670734/c-pointer-to-class-data-member (16认同)
  • @rlbond,我在这里对这个问题的评论中回答了你的问题:http://stackoverflow.com/questions/822059/sfinae-with-invalid-function-type-or-array-type-parameters.简而言之:如果两个测试函数都是候选者并且可行,则"......"具有最差的转换成本,因此永远不会采用,而是支持其他函数."..."是省略号,var-arg事物:int printf(char const*,...); (8认同)

odi*_*erd 13

在C++ 11中,SFINAE测试变得更加漂亮.以下是常见用途的几个示例:

根据特征选择函数重载

template<typename T>
std::enable_if_t<std::is_integral<T>::value> f(T t){
    //integral version
}
template<typename T>
std::enable_if_t<std::is_floating_point<T>::value> f(T t){
    //floating point version
}
Run Code Online (Sandbox Code Playgroud)

使用所谓的类型接收器习惯用法,您可以对类型执行相当任意的测试,例如检查它是否具有成员以及该成员是否属于某种类型

//this goes in some header so you can use it everywhere
template<typename T>
struct TypeSink{
    using Type = void;
};
template<typename T>
using TypeSinkT = typename TypeSink<T>::Type;

//use case
template<typename T, typename=void>
struct HasBarOfTypeInt : std::false_type{};
template<typename T>
struct HasBarOfTypeInt<T, TypeSinkT<decltype(std::declval<T&>().*(&T::bar))>> :
    std::is_same<typename std::decay<decltype(std::declval<T&>().*(&T::bar))>::type,int>{};


struct S{
   int bar;
};
struct K{

};

template<typename T, typename = TypeSinkT<decltype(&T::bar)>>
void print(T){
    std::cout << "has bar" << std::endl;
}
void print(...){
    std::cout << "no bar" << std::endl;
}

int main(){
    print(S{});
    print(K{});
    std::cout << "bar is int: " << HasBarOfTypeInt<S>::value << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

这是一个现场的例子:http://ideone.com/dHhyHE 我最近也在我的博客上写了关于SFINAE和标签发送的整个部分(无耻插件但相关)http://metaporky.blogspot.de/2014/08/部分7-静态调度,function.html

注意,从C++ 14开始,有一个std :: void_t,它与我的TypeSink基本相同.

  • 我是模板元编程的新手,所以我想了解这个例子。有没有理由在一个地方使用 `TypeSinkT&lt;decltype(std::declval&lt;T&amp;&gt;().*(&amp;T::bar))&gt;` 然后在另一个地方使用 `TypeSinkT&lt;decltype(&amp;T::bar)&gt;` ? `std::declval&lt;T&amp;&gt;` 中的 `&amp;` 也是必要的吗? (2认同)
  • 关于你的 `TypeSink`,C++17 有 [`std::void_t`](https://en.cppreference.com/w/cpp/types/void_t) :) (2认同)

Dav*_*ner 9

Boost的enable_if库为使用SFINAE提供了一个很好的干净界面.我最喜欢的用法示例之一是Boost.Iterator库.SFINAE用于启用迭代器类型转换.


小智 7

在我看来,其他答案提供的示例比需要的更复杂。

这是cppreference中稍微容易理解的示例:

#include <iostream>
 
// this overload is always in the set of overloads
// ellipsis parameter has the lowest ranking for overload resolution
void test(...)
{
    std::cout << "Catch-all overload called\n";
}
 
// this overload is added to the set of overloads if
// C is a reference-to-class type and F is a pointer to member function of C
template <class C, class F>
auto test(C c, F f) -> decltype((void)(c.*f)(), void())
{
    std::cout << "Reference overload called\n";
}
 
// this overload is added to the set of overloads if
// C is a pointer-to-class type and F is a pointer to member function of C
template <class C, class F>
auto test(C c, F f) -> decltype((void)((c->*f)()), void())
{
    std::cout << "Pointer overload called\n";
}
 
struct X { void f() {} };
 
int main(){
  X x;
  test( x, &X::f);
  test(&x, &X::f);
  test(42, 1337);
}
Run Code Online (Sandbox Code Playgroud)

输出:

Reference overload called
Pointer overload called
Catch-all overload called
Run Code Online (Sandbox Code Playgroud)

如您所见,在第三次调用 test 时,替换失败且没有错误。


who*_*oan 5

这是另一个(已故的)SFINAE示例,基于Greg Rogers回答

template<typename T>
class IsClassT {
    template<typename C> static bool test(int C::*) {return true;}
    template<typename C> static bool test(...) {return false;}
public:
    static bool value;
};

template<typename T>
bool IsClassT<T>::value=IsClassT<T>::test<T>(0);
Run Code Online (Sandbox Code Playgroud)

通过这种方式,您可以检查 的value值以查看是否T是一个类:

int main(void) {
    std::cout << IsClassT<std::string>::value << std::endl; // true
    std::cout << IsClassT<int>::value << std::endl;         // false
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

  • 它是一个指向成员的指针。一些参考:https://isocpp.org/wiki/faq/pointers-to-members (2认同)