13

我有以下代码片段,无法编译。

#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

4 回答 4

6

下面给出了在https://ideone.com/mxIVw3上发布的针对您的问题的有效解决方案- 另请参见实时示例

这个问题在某种意义上是C++中继承方法推导父类的后续。在我的回答中,我定义了一个类型特征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()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

前两个特征需要更多的专业化,以使不同的限定符更通用,例如const/volatile或引用限定符。有 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 };
};

给定类型Z,别名模板获取成员指针pattern的正确类型,提取其'ed 类和签名,并合成一个具有类和签名的新指针到成员函数类型,即。这正是您所需要的:它与您的原始硬编码模式相同,类型由正确的类(可能是基类)替换,如.Mdecltype(&Z::foo)decayCSC constvoid()void (C::*)() constZCdecltype

图形化:

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

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

于 2014-05-06T17:42:36.700 回答
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;
}

输出(演示):

1
1
0
0

希望有帮助。

于 2014-05-06T17:25:17.107 回答
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;
};

现场示例在这里

编辑 :

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

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

它是内部逗号运算符左侧的时髦表达式decltype使其发生。它可能是不正确的,当

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

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

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

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

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


但是,此解决方案存在潜在问题。考虑:

struct X {
    void foo() const;
};

struct Y : X {
    int foo();   // hides X::foo
};

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

希望有帮助。

于 2014-05-07T16:23:03.383 回答
0

我建议使用decltype一般确定成员函数指针的类型

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

这看起来像是违反了DRY,但从根本上说,重复名称并不比从名称中单独指定类型更糟糕。

于 2014-05-06T17:47:24.107 回答