关于模板:C ++ SFINAE示例?

C++ SFINAE examples?

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


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

1
2
3
4
5
6
7
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 */
}

它可能非常有用。例如,我使用它来检查使用运算符逗号收集的初始值设定项列表是否不再是固定大小

1
2
3
4
5
template<int N>
struct Vector {
    template<int M>
    Vector(MyInitList<M> const& i, char(*)[M <= N] = 0) { /* ... */ }
}

仅当m小于n时才接受该列表,这意味着初始值设定项列表没有太多元素。

语法char(*)[C]表示:指向元素类型为char、大小为C的数组的指针。如果C为false(这里是0),那么我们得到无效的char(*)[0]类型,指向一个零大小数组的指针:sfinae使其成为这样,模板将被忽略。

boost::enable_if表示,如下

1
2
3
4
5
6
template<int N>
struct Vector {
    template<int M>
    Vector(MyInitList<M> const& i,
           typename enable_if_c<(M <= N)>::type* = 0) { /* ... */ }
}

在实践中,我经常发现检查条件的能力是一种有用的能力。


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

1
2
3
4
5
6
7
8
9
10
11
12
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 };
};

当计算IsClassT::Yes时,0不能转换为int int::*,因为int不是类,所以它不能有成员指针。如果sfinae不存在,那么您将得到一个编译器错误,例如"0不能转换为非类类型int的成员指针"。相反,它只使用返回2的...形式,因此计算结果为false,int不是类类型。


在C++ 11中,SIMEAE测试变得更加美观。以下是一些常用的例子:

根据特性选择函数过载

1
2
3
4
5
6
7
8
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
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//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;
}

以下是一个实时示例:http://ideone.com/dhhyhe我最近还在我的博客(不知羞耻的插件,但相关)中写了一整节关于sfinae和tag-dispatch的文章http://metaporky.blogspot.de/2014/08/part-7-static-dispatch-function.html

注意到C++ 14中有一个STD::VoIDyt,它本质上和我的TypeSink在这里是一样的。


Boost的Enable_if库为使用sfinae提供了一个干净的界面。我最喜欢的用法示例之一是boost.iterator库。sfinae用于启用迭代器类型转换。


C++ 17可能提供了一种通用的查询特征的方法。有关详细信息,请参见N4502,但作为一个独立的示例,请考虑以下内容。

这部分是常量部分,放在标题中。

1
2
3
4
5
6
7
8
9
10
11
// See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf.
template <typename...>
using void_t = void;

// Primary template handles all types not supporting the operation.
template <typename, template <typename> class, typename = void_t<>>
struct detect : std::false_type {};

// Specialization recognizes/validates only types supporting the archetype.
template <typename T, template <typename> class Op>
struct detect<T, Op, void_t<Op<T>>> : std::true_type {};

以下示例取自N4502,显示了用法:

1
2
3
4
5
6
7
// Archetypal expression for assignment operation.
template <typename T>
using assign_t = decltype(std::declval<T&>() = std::declval<T const &>())

// Trait corresponding to that archetype.
template <typename T>
using is_assignable = detect<T, assign_t>;

与其他实现相比,这个实现相当简单:一组简化的工具(void_tdetect就足够了。此外,据报道(参见N4502),它比以前的方法更有效(编译时和编译器内存消耗)。

下面是一个实况示例,其中包括GCCPre5.1的可移植性调整。


下面是另一个(后期)斯芬娜的例子,基于格雷格·罗杰斯的回答:

1
2
3
4
5
6
7
8
9
10
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);

这样,您可以检查value的值,看T是否是一个类:

1
2
3
4
5
int main(void) {
    std::cout << IsClassT<std::string>::value << std::endl; // true
    std::cout << IsClassT<int>::value << std::endl;         // false
    return 0;
}


这里有一篇很好的SFIEAE文章:C++的SFIEAE概念的介绍:类成员的编译时内省。

总结如下:

1
2
3
4
5
6
7
8
9
10
11
12
/*
 The compiler will try this overload since it's less generic than the variadic.
 T will be replace by int which gives us void f(const int& t, int::iterator* b = nullptr);
 int doesn't have an iterator sub-type, but the compiler doesn't throw a bunch of errors.
 It simply tries the next overload.
*/

template <typename T> void f(const T& t, typename T::iterator* it = nullptr) { }

// The sink-hole.
void f(...) { }

f(1); // Calls void f(...) { }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template<bool B, class T = void> // Default template version.
struct enable_if {}; // This struct doesn't define"type" and the substitution will fail if you try to access it.

template<class T> // A specialisation used if the expression is true.
struct enable_if<true, T> { typedef T type; }; // This struct do have a"type" and won't fail on access.

template <class T> typename enable_if<hasSerialize<T>::value, std::string>::type serialize(const T& obj)
{
    return obj.serialize();
}

template <class T> typename enable_if<!hasSerialize<T>::value, std::string>::type serialize(const T& obj)
{
    return to_string(obj);
}

declval是一个实用程序,它为您提供了对一个类型的对象的"假引用",该类型的对象不容易构造。declval对于我们的sfinae结构非常方便。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct Default {
    int foo() const {return 1;}
};

struct NonDefault {
    NonDefault(const NonDefault&) {}
    int foo() const {return 1;}
};

int main()
{
    decltype(Default().foo()) n1 = 1; // int n1
//  decltype(NonDefault().foo()) n2 = n1; // error: no default constructor
    decltype(std::declval<NonDefault>().foo()) n2 = n1; // int n2
    std::cout <<"n2 =" << n2 << '
'
;
}

自上次对此线程的答复后,存在一个新的日志。

它是流畅的C++:HTTP:/FLUENTCPP.COM/

关于"sfinae"的研究有很多例子。