【问题标题】:How can I check whether a member function has const overload?如何检查成员函数是否有 const 重载?
【发布时间】:2016-02-11 19:39:42
【问题描述】:

假设我有

struct foo {
    void ham() {}
    void ham() const {}
};

struct bar {
    void ham() {}
};

假设我有一个模板化函数,我能否判断给定类型是否有 ham 的 const 重载?

【问题讨论】:

  • 你想完成什么?这有什么用例?
  • @NathanOliver 说来话长,但基本的想法是有一个自动检查器,它提供比编译错误更友好的错误消息,同时最大限度地减少我必须做的单独编译的数量。
  • 好的。对我来说,它看起来像 XY problem,所以我想我会问。
  • @NathanOliver 当然,没问题。

标签: c++ templates c++11


【解决方案1】:

#define DEFINE_HAS_SIGNATURE(traitsName, funcName, signature)               \
    template <typename U>                                                   \
    class traitsName                                                        \
    {                                                                       \
    private:                                                                \
        template<typename T, T> struct helper;                              \
        template<typename T>                                                \
        static std::uint8_t check(helper<signature, &funcName>*);           \
        template<typename T> static std::uint16_t check(...);               \
    public:                                                                 \
        static                                                              \
        constexpr bool value = sizeof(check<U>(0)) == sizeof(std::uint8_t); \
    }

DEFINE_HAS_SIGNATURE(has_ham_const, T::ham, void (T::*)() const);

然后

static_assert(has_ham_const<foo>::value, "unexpected");
static_assert(!has_ham_const<bar>::value, "unexpected");

Demo

【讨论】:

  • 不使用丑陋的宏就不行吗?
  • 是的,老式 SFINAE 会检查 exact 签名。我有时会问自己:它是否也可以扩展到任意返回类型(没有表达式 sfinae a la void_t)?
  • 谢谢,这行得通,但我第二个 davidhigh 的问题。这可以扩展为允许不同的返回类型吗?
  • @davidhigh 查看我对不关心返回类型的版本的回答。
  • @Rumburak:是的,但那是表达 sfinae。
【解决方案2】:

SFINAE 一遍又一遍。这是另一个未指定返回类型但允许您指定参数的选项。

(为了比较:@Jarod42 的方法检查确切的签名、返回类型 + 参数,其他 void_t 表达式 sfinae 到目前为止只检查是否可以调用 ham()。)

另外,它适用于当前的 MSVC 2015 Update 1 版本(与通常的 void_t 不同)。

template<typename V, typename ... Args>
struct is_callable_impl
{
    template<typename C> static constexpr auto test(int) -> decltype(std::declval<C>().ham(std::declval<Args>() ...), bool{}) { return true; }
    template<typename> static constexpr auto test(...) { return false; }
    static constexpr bool value = test<V>(int{});
    using type = std::integral_constant<bool, value>;
};

template<typename ... Args>
using is_callable = typename is_callable_impl<Args...>::type;

把它当做

struct foo
{
    void ham() {}
    void ham() const {}
    int ham(int) const {}
};

int main()
{
     std::cout
      <<is_callable<foo>::value                //true
      <<is_callable<const foo>::value          //true
      <<is_callable<const foo, int>::value     //true
      <<is_callable<const foo, double>::value  //also true, double is converted to int
      <<is_callable<const foo, std::string>::value  //false, can't call foo::ham(std::string) const
      <<std::endl;
}

Demo on Coliru

不过,对于“最新”的 sfinae 内容,我建议您查看 boost.hana

【讨论】:

  • 由于 MSVC 兼容性,我最终接受了这个答案。
  • 关于 MSVC 的兼容性,也可以看看new clang plugin。这真的很酷。
【解决方案3】:

检测器(如is_detected):

template <typename...>
using void_t = void;

template <typename T, template <typename> class D, typename = void>
struct detect : std::false_type {};

template <typename T, template <typename> class D>
struct detect<T, D, void_t<D<T>>> : std::true_type {};

示例成员验证器:

template <typename T>
using const_ham = decltype(std::declval<const T&>().ham());

测试:

static_assert(detect<foo, const_ham>::value, "!");
static_assert(!detect<bar, const_ham>::value, "!");

DEMO

【讨论】:

    【解决方案4】:

    另一个选项是模拟void_t(正式出现在 C++17 中),它使用expression SFINAE 来确保您的函数在const 实例上是可调用的,无论其返回类型如何.

    #include <iostream>
    #include <type_traits>
    
    struct Foo
    {
        void ham() const;
        void ham();
    };
    
    struct Bar {
        void ham() {}
    };
    
    template<typename...>
    using void_t = void;
    
    template<typename C, typename = void>
    struct has_const_ham: std::false_type{};
    
    template<typename C> // specialization, instantiated when there is ham() const
    struct has_const_ham<C, void_t<decltype(std::declval<const C&>().ham())>> : 
        std::true_type{};
    
    int main()
    {
        std::cout << std::boolalpha;
        std::cout << has_const_ham<Foo>::value << std::endl;
        std::cout << has_const_ham<Bar>::value << std::endl;
    }
    

    编辑

    如果您想强制返回类型,则从std::is_same 派生特化,例如

    template<typename C> // specialization, instantiated when there is ham() const
    struct has_const_ham<C, void_t<decltype(std::declval<const C&>().ham())>> : 
        std::is_same<decltype(std::declval<const C&>().ham()), void> // return must be void
    {}; 
    

    Live on Coliru

    【讨论】:

    • @davidhigh 谢谢!我认为所有的答案都很好,可能 OP 有足够的材料来消化;)
    • 非常迂腐:在std::declval&lt;const C&amp;&gt; 中的引用是unneeded
    • @davidhigh 哦,是的,这是一个未评估的上下文;)嗯...嗯...我认为declval() 返回add_rvalue_reference,后者使用参考折叠规则。所以从技术上讲,declval&lt;const T&amp;&gt;() 的结果是const T&amp;,而declval&lt;const T&gt;() 返回const T&amp;&amp;。现在,如果您的函数是 ref-qualified,它将有所作为。但无论如何,这是超级详细的。
    • 完全正确,感谢您指出这一点。我一直以为会是add_*l*value_reference,因此从未仔细阅读过。但是,那么您基本上想在没有引用的情况下调用std::declval,否则您将失去对 ref-qualifier 提出质疑的机会(它将始终是 &amp;)。我对此的最后评论,这真的没那么重要:-)
    【解决方案5】:

    这是一个没有宏的解决方案,它也不关心返回类型:

    template <typename T>
    struct is_well_formed : std::true_type
    {
    };
    
    template <typename T, typename = void>
    struct has_const_ham : std::false_type
    {
    };
    
    template <typename T>
    struct has_const_ham<T,
                         typename std::enable_if<is_well_formed<decltype(
                             std::declval<const T&>().ham())>::value>::type>
        : std::true_type
    {
    };
    
    
    static_assert(has_const_ham<foo>::value, "oops foo");
    static_assert(!has_const_ham<bar>::value, "oops bar");
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-11-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-02-23
      • 2019-09-07
      • 2016-02-26
      • 1970-01-01
      相关资源
      最近更新 更多