检查模板化成员函数是否存在 SFINAE

Check if templated member function exists SFINAE

以下问题: 我想检查模板化方法是否存在,因此我修改了此处给出的示例: Is it possible to write a template to check for a function's existence?

#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;
}

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

std::false_type testExistence(...);

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

的不明确存在而出错

but unfortunately the static_assert is triggered for init (as the specialization is probably evaluated at a later time as the object is being instantiated in main())

不完全是。

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

decltype(testSignature(&MemberPointerType::FUNCTION))

没有指针被 select 编辑,因为编译器无法 select 正确的方法。

你可以试试

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");

你从 CIfCheck_foo 得到一个 false 值,因为 Bla 中没有 void(&BLA::*)(int) 类型的方法 foo (有一个 void(&BLA::*)(long) 那是不一样的)。

用我的方法你可以从 CIfCheck_foo 得到一个 true 值,因为 foo(long) 也接受一个 int 值( returned 类型是 void).


std::false_type testExistence(...);

Why exactly do I have to pass an argument here? If I remove the variadic argument ... option (and nullptr and nullptr_t), the compiler errors due to ambiguous existence of testExistence().

testExistence(),如

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

第二选择

我的意思是...当您在 decltype()

中调用 testExistence() 宏时
decltype(testExistence<ClassType>(nullptr));

或者我在 decltype()

中调用 testSig() 的宏
decltype(testSig<ClassType>(wrap<__VA_ARGS__>{}));

调用带参数的函数(nullptrwrap<__VA_ARGS__>{})。

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

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

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

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