我有以下代码片段,无法编译:
#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拒绝了,我不知道谁是对的。
任何想法?
编辑:由于许多评论要求更具体的问题版本,我将在这里写下来:
我正在寻找的是一种显式检查类是否遵守特定接口的方法。此检查将用于验证模板化函数中的输入参数,以便它们尊重这些函数所需的契约,以便在类和函数不兼容的情况下提前停止编译(即类型特征类型的检查)。
因此,我需要能够验证我请求的每个成员函数的返回类型、参数类型和编号、constness等。最初的问题是我用来验证匹配的更大模板的检查部分。
下面给出了在https://ideone.com/mxIVw3上发布的问题的工作解决方案-参见另一个实例。
这个问题在某种意义上是c++中推导继承方法的父类的后续问题。在我的回答中,我定义了一个类型trait 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
或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 };
};
给定类型Z
,别名模板pattern
通过decltype(&Z::foo)
获取成员指针的正确类型M
,提取其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
事实上,这里不需要签名S
,所以member_type
也不需要。但是我在这个过程中使用了它,所以我把它包括在这里是为了完整。它可能在更一般的情况下有用。
当然,所有这些对于多重重载不起作用,因为decltype
在这种情况下不起作用。
如果您只是想检查给定类型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
希望对你有帮助。
下面是一个通过测试的简单类(并且不需要大量的专门化:))。当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
的重载。两者都可以接受整数字量作为参数,因为第二个方法在参数列表中有一个省略号,当两个重载都可行时,它永远不会是重载解析的最佳可行方法( ellipsis -conversion-sequence比任何其他转换序列都差)。这使得我们稍后可以明确地初始化trait类的value
成员。
只有当第一个过载从过载集中被丢弃时才选择第二个过载。当模板参数替换失败且不是错误(SFINAE)时,会发生这种情况。
这是decltype
中逗号操作符左侧的时髦表达式,使其发生。当
子表达式
&U::foo
格式错误,这可能发生在-
U
不是类类型,或者 -
U::foo
不可访问,或者 - 没有
U::foo
-
结果成员指针不能是
static_cast
到目标类型
注意,当U::foo
本身是不明确的时候,查找&U::foo
不会失败。这在c++标准13.4
(重载函数的地址,[over.over])中列出的特定上下文中是有保证的。一个这样的上下文是显式类型转换(在本例中为static_cast
)。
表达式还利用了T B::*
可转换为T D::*
的事实,其中D
是从B
派生的类(但不是相反)。这样就不需要像iavr的答案那样推断类类型了。
true_type
或false_type
中的value
初始化value
成员
但是,这个解决方案有一个潜在的问题。考虑:
struct X {
void foo() const;
};
struct Y : X {
int foo(); // hides X::foo
};
现在is_foo<Y>::value
将给出false
,因为foo
的名称查找将在遇到Y::foo
时停止。如果这不是您想要的行为,请考虑将您希望在其中执行查找的类作为is_foo
的模板参数传递,并使用它来代替&U::foo
。
希望有帮助。
我建议使用decltype
来通用地确定成员函数指针的类型:
helper<decltype(&A::foo), &A::foo> compiles;
helper<decltype(&B::foo), &B::foo> also_compiles;
这似乎违反了DRY,但从根本上说,重复名称并不比单独指定类型更糟糕。