C++ 如何检查成员函数是否具有常量重载?

C++ 如何检查成员函数是否具有常量重载?,c++,templates,c++11,C++,Templates,C++11,假设我有 struct foo { void ham() {} void ham() const {} }; struct bar { void ham() {} }; 假设我有一个模板函数,我能告诉给定的类型是否有一个常量重载用于ham? #define DEFINE_HAS_SIGNATURE(traitsName, funcName, signature) \ template <typename U>

假设我有

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

struct bar {
    void ham() {}
};
假设我有一个模板函数,我能告诉给定的类型是否有一个常量重载用于
ham

#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);
\define\u具有签名(traitsName、funcName、签名)\
模板\
类traitsName\
{                                                                       \
私人:\
模板结构助手\
模板\
静态标准::uint8_t检查(helper*)\
模板静态标准::uint16_t检查(…)\
公众:\
静止的\
constexpr bool value=sizeof(检查(0))==sizeof(标准::uint8_t)\
}
定义签名(HAS_ham_const,T::ham,void(T::*)()const);
然后

static_assert(has_ham_const<foo>::value, "unexpected");
static_assert(!has_ham_const<bar>::value, "unexpected");
static_assert(具有_ham_const::value,“意外”);
静态断言(!has_ham_const::value,“意外”);

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

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");
模板
结构形式良好:std::true\u类型
{
};
模板
结构具有\u const\u ham:std::false\u类型
{
};
模板
结构有常数
:std::true\u类型
{
};
静态断言(has_const_ham::value,“oops foo”);
静态断言(!has_const_ham::value,“oops bar”);
检测器(类似):

模板
使用void\u t=void;
模板
结构检测:std::false_type{};
模板
结构检测:std::true_type{};
样本成员验证人:

template <typename T>
using const_ham = decltype(std::declval<const T&>().ham());
模板
使用const_ham=decltype(std::declval().ham());
测试:

static_断言(detect::value,“!”);
静态断言(!detect::value,“!”);

另一个选项是模拟(在C++17中正式出现),它利用来确保您的函数可以在
常量
实例上调用,而不管其返回类型如何

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

一次又一次的微笑。下面是另一个未指定返回类型的选项,但允许您指定参数

(用于比较:@Jarod42的方法检查确切的签名、返回类型+参数,另一个
void\t
表达式sfinae stuff到目前为止只检查是否可以调用
ham()

另外,它可以与当前版本一起使用(与通常的
void\t
stuff不同)

模板
结构是可调用的
{
模板静态constexpr自动测试(int)->decltype(std::declval().ham(std::declval


为了“最新的”但是,我建议你看看sfinae的东西。

你想做什么?这个用例是什么?@NathanOliver说来话长,但基本思想是有一个自动检查程序,它比编译错误更友好,同时最大限度地减少我必须进行的单独编译。好的,看起来ed喜欢我的签名,所以我想我会问。@NathanOliver当然,没问题。不使用难看的宏是不行的?是的,老派的SFINAE检查准确的签名。我有时问自己:它也可以扩展到任意返回类型(没有表达式SFINAE la
void\t
)?谢谢,这很有效,但我同意davidhigh的问题。这可以扩展到允许不同的返回类型吗?@davidhigh查看我的答案,以获得一个不关心返回类型的版本。@Rumburak:是的,但这是表达式sfinae。@davidhigh谢谢!我认为所有的答案都很好,而且OP可能有足够的材料可以消化;)非常迂腐:在
std::declval
中,引用是。@davidhigh Ohh是的,这是一个未经评估的上下文;)嗯……嗯……我认为
declval()
返回
add\rvalue\u reference
,后者使用引用折叠规则。因此从技术上讲,
declval()
会导致
常量和
,而
declval()
返回
常量T&&
。现在,如果您的函数是ref限定的,它将产生不同。但无论如何,这是超级重复详细的。完全正确,感谢您指出。我一直认为它将是
add_*l*value_reference
,因此永远不会仔细阅读它。但是,您基本上想调用e> std::declval
没有引用,否则您将失去对ref限定符提问的机会(它将始终是
&
).我对这个问题的最后评论,其实没那么重要:-)我最终接受了这个答案,因为MSVC的兼容性。关于MSVC的兼容性,也看看。这真的很酷。
static_assert(detect<foo, const_ham>::value, "!");
static_assert(!detect<bar, const_ham>::value, "!");
#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;
}
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
{}; 
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;
}