匹配继承的成员函数的类型

Sva*_*zen 13 c++ match member-pointers sfinae c++11

我有以下剪切的代码,不编译.

#include <iostream>

struct A {
    void foo() {}
};

struct B : public A {
    using A::foo;
};

template<typename U, U> struct helper{};

int main() {
    helper<void (A::*)(), &A::foo> compiles;
    helper<void (B::*)(), &B::foo> does_not_compile;

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

它自&B::foo解析后不编译&A::foo,因此无法与提议的类型匹配void (B::*)().由于这是SFINAE模板的一部分,我用它来检查一个非常具体的接口(我强制使用特定的参数类型和输出类型),我希望这可以独立于继承,同时保持检查的可读性.

我尝试的内容包括:

  • 抛出论证的第二部分:

    helper<void (B::*)(), (void (B::*)())&B::foo> does_not_compile;

    遗憾的是,这并没有帮助,因为第二部分现在不被认为是一个常量表达式,并且失败了.

  • 我已经尝试为变量分配引用,以便检查它.

    constexpr void (B::* p)() = &B::foo; helper<void (B::* const)(), p> half_compiles;

    这个代码被clang 3.4接受,但g ++ 4.8.1拒绝它,我不知道谁是对的.

有任何想法吗?

编辑:由于许多评论要求更具体的问题版本,我会在这里写:

我正在寻找的是一种明确检查类是否尊重特定接口的方法.此检查将用于验证模板化函数中的输入参数,以便它们遵守这些函数所需的合约,以便在类和函数不兼容的情况下(即类型特征类型的检查)预先停止编译.

因此,我需要能够验证我请求的每个成员函数的返回类型,参数类型和数量,常量等.最初的问题是我用来验证匹配的更大模板的检查部分.

iav*_*avr 6

下面给出了https://ideone.com/mxIVw3上发布的问题解决方案- 请参阅实例.

这个问题在某种意义上是对C++Deduce父类继承方法的跟进.在我的回答中,我定义了一个类型特征member_class,它从给定成员函数类型的指针中提取一个类.下面我们使用一些更多的特征来分析然后合成这样的类型.

首先,member_type提取签名,例如void (C::*)()给出void():

template <typename M> struct member_type_t { };
template <typename M> using  member_type = typename member_type_t <M>::type;

template <typename T, typename C>
struct member_type_t <T C::*> { using type = T;};
Run Code Online (Sandbox Code Playgroud)

然后,member_class提取类,例如void (C::*)()给出C:

template<typename>
struct member_class_t;

template<typename M>
using member_class = typename member_class_t <M>::type;

template<typename R, typename C, typename... A>
struct member_class_t <R(C::*)(A...)> { using type = C; };

template<typename R, typename C, typename... A>
struct member_class_t <R(C::*)(A...) const> { using type = C const; };

// ...other qualifier specializations
Run Code Online (Sandbox Code Playgroud)

最后,member_ptr在给定类和签名的情况下合成指向成员函数类型的指针,例如C+ void()give void (C::*)():

template <typename C, typename S>
struct member_ptr_t;

template <typename C, typename S>
using member_ptr = typename member_ptr_t <C, S>::type;

template <typename C, typename R, typename ...A>
struct member_ptr_t <C, R(A...)> { using type = R (C::*)(A...); };

template <typename C, typename R, typename ...A>
struct member_ptr_t <C const, R(A...)> { using type = R (C::*)(A...) const; };

// ...other qualifier specializations
Run Code Online (Sandbox Code Playgroud)

之前的两个特征需要更多专业化,以使不同的限定符更通用,例如const/volatile或ref-qualifiers.有12种组合(或13种包括数据成员); 这里有一个完整的实现.

这个想法是任何限定符都是member_class从指向成员函数类型转移到类本身.然后member_ptr将类中的限定符传递回指针类型.虽然限定符属于类类型,但可以使用标准特征自由操作,例如添加或删除const,左值/右值引用等.

现在,这是你的is_foo测试:

template <typename T>
struct is_foo {
private:
    template<
        typename Z,
        typename M = decltype(&Z::foo),
        typename C = typename std::decay<member_class<M>>::type,
        typename S = member_type<M>
    >
    using pattern = member_ptr<C const, void()>;

    template<typename U, U> struct helper{};

    template <typename Z> static auto test(Z z) -> decltype(
        helper<pattern<Z>, &Z::foo>(),
        // All other requirements follow..
        std::true_type()
    );

    template <typename> static auto test(...) -> std::false_type;

public:
    enum { value = std::is_same<decltype(test<T>(std::declval<T>())),std::true_type>::value };
};
Run Code Online (Sandbox Code Playgroud)

给定类型Z,别名模板pattern得到正确的类型M与成员指针decltype(&Z::foo),抽取其decay"编班C和签名S,并合成新的指针到成员函数型带班C const和签名void(),即void (C::*)() const.这正是您所需要的:它与您原始的硬编码模式相同,其类型Z由正确的类C(可能是基类)替换,如下所示decltype.

图形:

M = void (Z::*)() const  ->   Z         +   void()
                         ->   Z const   +   void()
                         ->   void (Z::*)() const   ==   M
                         ->   SUCCESS

M = int (Z::*)() const&  ->   Z const&   +   int()
                         ->   Z const    +   void()
                         ->   void (Z::*)() const   !=   M
                         ->   FAILURE
Run Code Online (Sandbox Code Playgroud)

事实上,S这里不需要签名,所以也没有member_type.但是我在这个过程中使用了它,所以我把它包含在这里是为了完整性.它可能在更一般的情况下有用.

当然,所有这些都不适用于多次重载,因为decltype在这种情况下不起作用.


jro*_*rok 3

这是一个通过测试的简单类(并且不需要十几个专业化:))。foo超载时也能发挥作用。您想要检查的签名也可以是模板参数(这是一件好事,对吧?)。

#include <type_traits>

template <typename T>
struct is_foo {
    template<typename U>
    static auto check(int) ->
    decltype( static_cast< void (U::*)() const >(&U::foo), std::true_type() );
    //                     ^^^^^^^^^^^^^^^^^^^
    //                     the desired signature goes here

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

    static constexpr bool value = decltype(check<T>(0))::value;
};
Run Code Online (Sandbox Code Playgroud)

活生生的例子在这里

编辑 :

我们有两个重载的check. 两者都可以采用整数文字作为参数,并且因为第二个在参数列表中有省略号,所以当两个重载都可行时,它永远不会是重载决策中最好的可行方案(省略号转换序列比任何其他转换序列都差) 。这让我们稍后可以明确地初始化value特征类的成员。

仅当第一个过载从过载集中被丢弃时,才会选择第二个过载。当模板参数替换失败并且不是错误时会发生这种情况(SFINAE)。

正是内部逗号运算符左侧的时髦表达式decltype使得这一切发生。当以下情况时它可能是格式错误的:

  1. 子表达式的格式&U::foo不正确,这可能发生在

    • U不是类类型,或者
    • U::foo无法访问,或者
    • 没有U::foo
  2. 生成的成员指针不能指向static_cast目标类型

请注意,当查找本身不明确时,查找&U::foo不会失败。U::foo这在 C++ 标准13.4重载函数的地址,[over.over])下列出的某些上下文中得到保证。其中一种上下文是显式类型转换(static_cast在本例中)。

T B::*该表达式还利用了可转换为T D::*whereD是派生自的类的事实B(但反之则不然)。这样就不需要像iavr 的答案那样推导类类型。

valuevalue然后使用true_type或初始化成员false_type


不过,这个解决方案存在一个潜在的问题。考虑:

struct X {
    void foo() const;
};

struct Y : X {
    int foo();   // hides X::foo
};
Run Code Online (Sandbox Code Playgroud)

现在is_foo<Y>::value将给出false,因为名称查找foo将在遇到时停止Y::foo。如果这不是您想要的行为,请考虑将您希望在其中执行查找的类作为 的模板参数传递is_foo,并使用它来代替&U::foo

希望有帮助。