Module Check——Usage of std::enable_if

最近写代码的时候,总能在不少STL的函数声明里看见可爱的std::enable_if,虽然自己偶尔用过,不过也只是直接用STL库里的std::is_arithmetic之类。

于是就去Google了std::enable_if,然后参考了如下几篇post,写了一个较为通用的Module Check。

所以还是先从一个小的问题开始讲起吧~

假设我们做了一个中间类作为接口,这个中间类有一个模版构造函数,它接受一个类实例和一个double类型的参数,并且总是调用一个固定的实例方法。

class Intermediate {
public:
    template <typename T>
    Intermediate(const T& object, double value) {
        object.method(value);
    }
};

现在我们有如下的类,

class A {
public:
    void method(double d) const {
        std::cout<<d<<'\n';
    }
};

class B {
public:
    void method(int i) const {
        std::cout<<i<<'\n';
    }
};

虽然如下代码可以编译运行,但是假如我们希望只有类A被这样调用才有效呢?

Intermediate inter_a_1(a, 2.33); // print 2.33
Intermediate inter_b_1(b, 2.33); // print 2

如果仅仅是类A的话,我们或许还可以把构造函数写成这样

    template <typename T>
    Intermediate(const T& object, double value) {
        if (std::is_same<T, A>::value) {
            object.method(value);
        }
    }

但如果要求变为“只要类T有method函数,并且参数类型只能是double”呢?
即使这个中间类只有自己内部使用,并且已知所有的 有method函数,并且参数类型只能是double 的类名字,用一大堆std::is_same也勉强算是一个解决办法(如果你不嫌麻烦和难以维护的话)。可要是这个类是作为SDK提供给用户的呢?总不能想着靠注释就能禁止掉错误的使用方式吧?(╯°□°)╯︵ ┻━┻

于是,这里就需要std::enable_if登场了(=゚ω゚)ノ

不过首先呢,我们还需要一个辅助的struct

template <typename T>
struct module_check {
    typedef char (&no)  [1];
    typedef char (&yes) [2];

    template <typename U, void (T::*)(double) const>
    struct function_match;

    template <typename U>
    static yes check(function_match<U, &U::method>*); // best match

    template <typename U>
    static no check(...); // other

    enum {
        value = (sizeof(check<T>(0)) == sizeof(yes))
    };
};

首先typedef了yes和no,yes为2个字节,no为1个字节。然后这里利用了SFINAE,Substitution Failure Is Not An Error。即当我们的类T能够匹配上这个模版函数的话

    template <typename U>
    static yes check(function_match<U, &U::method>*); // best match

check就将是2字节长的。

否则统统会匹配上下面那个模版函数,当匹配上下面那个模版函数时,check就将是1字节长的。最后我们判断check的size即可

        value = (sizeof(check<T>(0)) == sizeof(yes))

现在可以请出我们的std::enable_if了,只需对刚才
Intermediate类的构造函数稍作改动即可。

    template <typename T, typename = typename std::enable_if<module_check_<T>::value>::type>
    Intermediate(const T& object, double value) {
        if (std::is_same<T, A>::value) {
            object.method(value);
        }
    }

这是我们会发现,原来以类B初始化的语句已经无法编译了

module_check_class_B_wont_compile

 

You shall not compile
You shall not compile

那么我们能不能再把它写得更通用一点呢?当然是可以的,不过这就得用上宏了,但是宏的使用不在本文的讨论范围之内,所以就直接贴代码了。(其实就是注意观察需要变化的部分,然后写成参数就好了)

实际使用时的效果

#include <iostream>
#include <type_traits>
#include "module_check.h"

class A {
public:
    void method(double d) const {
        std::cout<<d<<'\n';
    }
};

class B {
public:
    void method(int i) const {
        std::cout<<i<<'\n';
    }
};

module_check_t(perform_method_1, method);
module_check(perform_method_2, method, int);

class SmartClass {
public:
    template <typename T, typename = typename std::enable_if<module_check_perform_method_1<T, void, double>::value>::type>
    SmartClass(const T& object_with_arg_type_double, double value) {
        object_with_arg_type_double.method(value);
        std::cout<<"SmartClass init with object which can performs method(double)\n";
    }
};

class StupidClass {
public:
    template <typename T>
    StupidClass(const T& noob_object, double value) {
        noob_object.method(value);
    }
};

int main(int argc, const char * argv[]) {
    std::cout<<module_check_perform_method_1<A, void, double>::value<<'\n'; // true
    std::cout<<module_check_perform_method_1<A, void>::value<<'\n';         // false due to arg type does not match
    std::cout<<module_check_perform_method_1<B, void, int>::value<<'\n';    // true
    std::cout<<module_check_perform_method_1<B, void>::value<<'\n';         // false due to arg type does not match

    A a;
    SmartClass smart1(a, 2.33);
    SmartClass smart2(a, 233);      // 233 implicit cast to double
    StupidClass stupid1(a, 2.33);

    B b;
//    SmartClass smart3(b, 233);    // this line won't compile
//    SmartClass smart4(b, 2.33);   // this line won't compile
    StupidClass stupid2(b, 2.33); // 2.33 cast to int when method_1 called

    return 0;
}

输出如下:

1
0
1
0
2.33
SmartClass init with object which can performs method(double)
233
SmartClass init with object which can performs method(double)
2.33
2

最后就是module_check.h了,最新的版本在我的Github上,module_check

//
//  module_check.h
//  module_check
//
//  Created by Ryza 2016/8/16.
//  Copyright © 2016[data deleted]. All rights reserved.
//

#ifndef MODULE_CHECK_H
#define MODULE_CHECK_H

/**
 *  @brief Define a function to check whether a given class can perform a specific method or not
 *
 *  @discussion
 *         For instance, we have such classes

 *          class A {
 *           public:
 *              void method_1();
 *          };
 *          class B {
 *           public:
 *              void method_2(int);
 *          };
 *
 *         To check whether they can perform method or not, we can write
 *
 *          module_check(perform_method_1, method)
 *          module_check(perform_method_2, method, int)
 *
 *         The extra int behind 'method,' is the type of that method
 *
 *         Then two structs named module_check_perform_method_1 and module_check_perform_method_2 will be generated
 *         To use they
 *
 *          if (module_check_perform_method_1<A, void>::value) {
 *              // true
 *          }
 *          if (module_check_perform_method_1<A, int>::value) {
 *              // false due to return type does not match
 *          }
 *          if (module_check_perform_method_1<B, void>::value) {
 *              // false due to arg type does not match
 *          }
 *
 *          if (module_check_perform_method_2<B, void>::value) {
 *              // true
 *          }
 *          if (module_check_perform_method_2<B, int>::value) {
 *              // false due to return type does not match
 *          }
 *          if (module_check_perform_method_2<A, void>::value) {
 *              // false due to arg type does not match
 *          }
 *
 *         Or
 *
 *          class SmartClass {
 *           public:
 *              template <typename T, typename = typename std::enable_if<module_check_perform_method_2<T, void>::value, int>::type>
 *              SmartClass(const T& object_with_arg_type_int) {
 *                  object_with_arg_type_int.method_1(233);
 *              }
 *          };
 *
 *          //  A a;
 *          //  SmartClass smart1(a); // this line won't compile
 *
 *          B b;
 *          SmartClass smart2(b);
 *
 *  @note  This only applies to public method
 */

#define module_check(module_name, method_name, ...)\
template <typename T, typename R>\
struct module_check_##module_name {\
    typedef char (&no)  [1];\
    typedef char (&yes) [2];\
    template <typename U, R (T::*)(__VA_ARGS__)>\
    struct function_match;\
    template <typename U, R (T::*)(__VA_ARGS__) const>\
    struct function_match_const;\
    template <typename U>\
    static yes check(function_match<U, &U::method_name>*);\
    template <typename U>\
    static yes check(function_match_const<U, &U::method_name>*);\
    template <typename U>\
    static no check(...);\
    enum {\
        value = (sizeof(check<T>(0)) == sizeof(yes))\
    };\
};

/**
 *  @brief  A variant of module_check
 *
 *  @discussion Define a function to check whether a given class can perform a specific method (only with const qualified) or not
 */
#define module_check_const(module_name, method_name, ...)\
    template <typename T, typename R>\
    struct module_check_##module_name {\
    typedef char (&no)  [1];\
    typedef char (&yes) [2];\
    template <typename U, R (T::*)(__VA_ARGS__) const>\
    struct function_match_const;\
    template <typename U>\
    static yes check(function_match_const<U, &U::method_name>*);\
    template <typename U>\
    static no check(...);\
    enum {\
        value = (sizeof(check<T>(0)) == sizeof(yes))\
    };\
};

/**
 *  @brief Define a function to check whether a given class can perform a specific method or not
 *
 *  @discussion
 *         This macro acts pretty much like the one above, the only difference is that this accepts arg type in code
 *         For instance,

 *          class A {
 *           public:
 *              void method_1(double);
 *          };
 *          class B {
 *           public:
 *              void method_1(int);
 *          };
 *
 *         And we write
 *
 *          module_check_t(perform_method_1, method)
 *
 *         Then a struct named module_check_perform_method_1 will be generated
 *         To use it
 *
 *          if (module_check_perform_method_1<A, void, double>::value) {
 *              // true
 *          }
 *          if (module_check_perform_method_1<A, void>::value) {
 *              // false due to arg type does not match
 *          }
 *          if (module_check_perform_method_1<B, void, int>::value) {
 *              // true
 *          }
 *          if (module_check_perform_method_1<B, void>::value) {
 *              // false due to arg type does not match
 *          }
 *
 *         Or
 *
 *          class SmartClass {
 *           public:
 *              template <typename T, typename = typename std::enable_if<module_check_perform_method_1<T, void, double>::value, int>::type>
 *              SmartClass(const T& object_with_method_1_arg_type_double) {
 *                  object_with_method_1_arg_type_double.method_1(2.33);
 *              }
 *          };
 *
 *          A a;
 *          SmartClass smart1(a);
 *
 *          //    B b;
 *          //    SmartClass smart2(b); // this line won't compile
 *
 *  @note  This only applies to public method
 */
#define module_check_t(module_name, method_name)\
template <typename T, typename R, typename ... Args>\
struct module_check_##module_name {\
    typedef char (&no)  [1];\
    typedef char (&yes) [2];\
    template <typename U, R (T::*)(Args ...)>\
    struct function_match;\
    template <typename U, R (T::*)(Args ...) const>\
    struct function_match_const;\
    template <typename U>\
    static yes check(function_match<U, &U::method_name>*);\
    template <typename U>\
    static yes check(function_match_const<U, &U::method_name>*);\
    template <typename U>\
    static no check(...);\
    enum {\
        value = (sizeof(check<T>(0)) == sizeof(yes))\
    };\
};

/**
 *  @brief  A variant of module_check_t
 *
 *  @discussion Define a function to check whether a given class can perform a specific method (only with const qualified) or not
 */
#define module_check_const_t(module_name, method_name)\
template <typename T, typename R, typename ... Args>\
struct module_check_##module_name {\
    typedef char (&no)  [1];\
    typedef char (&yes) [2];\
    template <typename U, R (T::*)(Args ...) const>\
    struct function_match_const;\
    template <typename U>\
    static yes check(function_match_const<U, &U::method_name>*);\
    template <typename U>\
    static no check(...);\
    enum {\
        value = (sizeof(check<T>(0)) == sizeof(yes))\
    };\
};

#endif /* MODULE_CHECK_H */
 

Leave a Reply

Your email address will not be published. Required fields are marked *

4 × 2 =