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

时间:2014-05-06 16:02:53

标签: c++ c++11 match sfinae member-pointers

我有以下剪辑代码,不能编译。

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

&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拒绝了它,我不知道谁是对的。

有什么想法吗?

编辑:由于许多评论都要求提供更具体的问题版本,我将在此处写下:

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

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

4 个答案:

答案 0 :(得分:6)

下面给出了https://ideone.com/mxIVw3发布的问题解决方案 - 另见live example

这个问题在某种意义上是Deduce parent class of inherited method in C++的后续行动。在my answer中,我定义了一个类型特征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;};

然后,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

最后,member_ptr在给定类和签名的情况下合成指向成员函数类型的指针,例如C + void()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

之前的两个特征需要更多专业化,以使不同的限定符更通用,例如const/volatile或参考资格。有12种组合(或13种包括数据成员);完整的实施是here

这个想法是任何限定符都由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 };
};

给定类型Z,别名模板pattern使用M获取成员指针的正确类型decltype(&Z::foo),并提取其decay'ed类{{ 1}}和签名C,并使用类S和签名C const合成一个新的指向成员函数的指针类型,即void()。这正是您所需要的:它与原始的硬编码模式相同,类型void (C::*)() const被正确的类Z(可能是基类)替换,如{{1}所示}。

图形:

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 也不需要。但是我在这个过程中使用了它,所以我把它包含在这里是为了完整性。它可能在更一般的情况下有用。

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

答案 1 :(得分:4)

如果您只是想检查给定类型T上是否存在接口,那么就有更好的方法来实现它。这是一个例子:

template<typename T>
struct has_foo
{
    template<typename U>
    constexpr static auto sfinae(U *obj) -> decltype(obj->foo(), bool()) { return true; }

    constexpr static auto sfinae(...) -> bool { return false; }

    constexpr static bool value = sfinae(static_cast<T*>(0));
};

测试代码:

struct A {
    void foo() {}
};

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

struct C{};

int main() 
{
    std::cout << has_foo<A>::value << std::endl;
    std::cout << has_foo<B>::value << std::endl;
    std::cout << has_foo<C>::value << std::endl;
    std::cout << has_foo<int>::value << std::endl;
    return 0;
}

输出(demo):

1
1
0
0

希望有所帮助。

答案 2 :(得分: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;
};

直播示例here

编辑:

我们有两个check重载。两者都可以将整数文字作为参数,因为第二个文件在参数列表中有一个省略号,当两个重载都可行时,它永远不会是过载解析中最好的( elipsis-conversion-sequence 是比任何其他转换序列更糟糕)。这使我们可以稍后明确地初始化特征类的value成员。

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

这是decltype内部逗号运算符左侧的时髦表达式。

时可能会形成错误
  1. 子表达式&U::foo格式不正确,可能会在

    时发生
    • U不是类类型,或
    • U::foo无法访问,或
    • 没有U::foo
  2. 结果成员指针不能是static_cast到目标类型

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

    该表达式还利用T B::*可转换为T D::*的事实,其中D是从B派生的类(但不是相反)。这样就不需要像iavr's answer中那样推断出类类型。

    然后使用valuevalue的{​​{1}}初始化

    true_type成员。


    但这个解决方案存在潜在问题。考虑:

    false_type

    现在struct X { void foo() const; }; struct Y : X { int foo(); // hides X::foo }; 会给is_foo<Y>::value,因为false的名称查找会在遇到foo时停止。如果这不是您想要的行为,请考虑将要执行查找的类作为Y::foo的模板参数传递,并使用它代替is_foo

    希望有所帮助。

答案 3 :(得分:0)

我建议using decltype to generically determine the type of the member function pointers

helper<decltype(&A::foo), &A::foo> compiles;
helper<decltype(&B::foo), &B::foo> also_compiles;

这可能看起来像DRY违规行为,但重复这个名称从根本上说并不比从名称中单独指定类型更糟糕。