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

时间:2020-03-19 13:31:00

标签: c++ c++11 templates 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()的模棱两可而导致编译器错误。

1 个答案:

答案 0 :(得分:1)

但是不幸的是,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中没有类型foo的方法void(&BLA::*)(int)(有一个{{1 }}是不同的。

使用我的方法,您从void(&BLA::*)(long)获得了一个 true 值,因为CIfCheck_foo也接受了一个foo(long)值(返回的类型是int


std :: false_type testExistence(...);

为什么我必须在这里传递一个参数?如果我删除了可变参数void选项(以及...nullptr),则由于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。但是,此电话带有论点。这里的省略号是旧的C样式可变参数列表,可以接受零个或多个参数,因此也可以接受一个参数。

如果删除省略号(std::false),第二个选择将不再接受参数(成为零个参数的函数),并且由于编译器找不到与第二个选择的函数兼容而导致编译错误有争论。