【问题标题】:Check if templated member function exists SFINAE检查模板化成员函数是否存在 SFINAE
【发布时间】:2020-03-19 13:31:00
【问题描述】:

以下问题: 我想检查模板化方法是否存在,因此我调整了此处给出的示例: 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;
}

但不幸的是,init() 触发了 static_assert()(因为在 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() 的不明确存在而导致编译器错误。

【问题讨论】:

    标签: c++ c++11 templates sfinae


    【解决方案1】:

    但不幸的是,为 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&lt;void&gt;,因为std::tuple&lt;void&gt; 会出错。

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

    具体示例:假设有一个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(&amp;BLA::*)(int) 类型的方法foo(有一个不同的void(&amp;BLA::*)(long))。

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


    std::false_type testExistence(...);

    为什么我必须在这里传递一个参数?如果我删除可变参数... 选项(以及nullptrnullptr_t),由于testExistence() 的不明确存在而导致编译器错误。

    那个testExistence(),就像

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

    是第二个选择。

    我的意思是...当你在 decltype() 中宏调用 testExistence()

    decltype(testExistence<ClassType>(nullptr));
    

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

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

    使用参数(nullptrwrap&lt;__VA_ARGS__&gt;{})调用该函数。

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

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

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

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

    【讨论】:

    • 我不能直接使用 std::,所以必须编写我自己的 declval 实现,但这有效。你能帮我理解static std::false_type testSig (...);(也是上述问题的一部分)
    • @mbed_dev - 答案改进:新部分以“还要注意”开头。
    猜你喜欢
    • 2021-09-04
    • 1970-01-01
    • 1970-01-01
    • 2018-11-23
    • 1970-01-01
    • 2016-06-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多