首页 > 解决方案 > 检查模板化成员函数是否存在 SFINAE

问题描述

以下问题:我想检查模板化方法是否存在,因此我调整了此处给出的示例: 是否可以编写模板来检查函数是否存在?

#include <cstdio>
#include <type_traits>

#define CHECK4_MEMBER_FUNC(RETTYPE,FUNCTION,...) \
template <class ClassType> \
class CIfCheck_##FUNCTION \
{\
private: \
    template <class MemberPointerType> \
    static std::true_type testSignature(RETTYPE (MemberPointerType::*)(__VA_ARGS__)); \
\
    template <class MemberPointerType> \
    static std::false_type testExistence(...); \
   \
    template <class MemberPointerType> \
    static decltype(testSignature(&MemberPointerType::FUNCTION)) testExistence(std::nullptr_t); \
public: \
    using type = decltype(testExistence<ClassType>(nullptr));\
    static const bool value = type::value; \
};


    class Bla
    {
    public:
        template <typename SomeType>
        bool init(SomeType someValue)
        {
            ///
            return true;
        }

        void exec()
        {
            return;
        }
    };

    CHECK4_MEMBER_FUNC(bool, init, int);
    CHECK4_MEMBER_FUNC(void, exec, void);

int main()
{
  Bla blaObj;
  blaObj.init<int>(2);
  static_assert(CIfCheck_exec<Bla>::value, "no exec");
  static_assert(CIfCheck_init<Bla>::value, "no init");

  return 0;
}

但不幸的static_assert()是,触发了init()(因为​​特化可能会在稍后实例化对象时进行评估main())。

我尝试了明确的成员专业化,但它仍然失败:

template<>
bool Bla::init<int>(int item)
{
    int temp = item*2; // do with item something
    return false;
}

PS:附带问题(可能另一个问题主题会更有意义:

std::false_type testExistence(...);

为什么我必须在这里传递一个论点?如果我删除可变参数...选项(和nullptrand nullptr_t),编译器会由于testExistence().

标签: c++c++11templatessfinae

解决方案


但不幸的是,为 init 触发了 static_assert(因为在 main() 中实例化对象时,可能会在稍后评估特化)

不完全是。

问题是这init()是一个模板方法,所以当你写

decltype(testSignature(&MemberPointerType::FUNCTION))

没有选择指针,因为编译器无法选择正确的方法。

你可以试试

decltype(testSignature(&MemberPointerType::template FUNCTION<__VA_ARGS__>))

但现在不适用于exec()那不是模板方法

要同时使用模板和非模板方法...不是简单地通过可变参数宏传递,因为可变参数部分不能为空...但我建议如下

template <typename...>
struct wrap
 { };

#define CHECK4_MEMBER_FUNC(RETTYPE,FUN,...) \
template <class ClassType> \
class CIfCheck_##FUN \
{\
private: \
    template <typename MPT> \
    static auto testSig (wrap<void>) \
       -> std::is_same<decltype(std::declval<MPT>().FUN()),\
                       RETTYPE>; \
    \
    template <typename MPT, typename ... As> \
    static auto testSig (wrap<As...>) \
       -> std::is_same<decltype(std::declval<MPT>().FUN(std::declval<As>()...)), \
                       RETTYPE>; \
    \
    template <typename...> \
    static std::false_type testSig (...);\
    \
public: \
    using type = decltype(testSig<ClassType>(wrap<__VA_ARGS__>{}));\
    static const bool value = type::value; \
};

请注意,我添加了一个wrap结构来包装模板参数类型;通常使用std::tuple,但在这种情况下,我们需要wrap<void>因为std::tuple<void>给出错误。

另请注意,我的解决方案从另一个角度来看是不同的(根据您的具体需求,可能更好或更糟):您的解决方案检查是否存在具有完全签名的方法;我的解决方案检查是否存在可使用给定参数列表调用的方法。

具体例子:假设有一个Bla::foo()方法接受一个long

    void foo (long)
     { }

使用您的解决方案,如果您检查int参数

CHECK4_MEMBER_FUNC(void, foo, int);

static_assert( false == CIfCheck_foo<Bla>::value, "no foo with int");

你得到一个false值是CIfCheck_foo因为没有类型Bla的方法(有一个不同的方法)。foovoid(&BLA::*)(int)void(&BLA::*)(long)

使用我的方法,您可以从中获得一个true值,CIfCheck_foo因为foo(long)接受也是一个int值(并且返回的类型是void)。


std::false_type testExistence(...);

为什么我必须在这里传递一个论点?如果我删除可变参数...选项(和nullptrand nullptr_t),编译器会由于testExistence().

testExistence(),作为

    template <typename...> \
    static std::false_type testSig (...);\

是第二个选择。

我的意思是......当你testExistence()在里面进行宏调用时decltype()

decltype(testExistence<ClassType>(nullptr));

testSig()或者我在里面的宏调用decltype()

decltype(testSig<ClassType>(wrap<__VA_ARGS__>{}));

nullptr使用参数 (或)调用该函数wrap<__VA_ARGS__>{}

当第一个选项可用时(RETTYPE (MemberPointerType::*)(__VA_ARGS__)在您的情况下何时出现,在我的示例中何时可以调用具有所需参数的方法),编译器选择该版本并返回std::true_type(或std::is_same在我的代码中)。

但是当第一选择不可用时?

第二个选择,返回的版本std::false,是必需的。但是电话是有论据的。这里的省略号是旧的 C 风格的可变参数列表并接受零个或多个参数,因此也接受一个参数。

如果您删除省略号 ( ...),则第二选择不能再接受参数(成为零参数函数)并且您会收到编译错误,因为编译器找不到与参数兼容的第二选择函数。


推荐阅读