我有以下剪辑代码,不能编译。
#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拒绝了它,我不知道谁是对的。
有什么想法吗?
编辑:由于许多评论都要求提供更具体的问题版本,我将在此处写下:
我正在寻找的是一种明确检查某个类是否尊重特定接口的方法。此检查将用于验证模板化函数中的输入参数,以便它们遵守这些函数所需的合同,以便在类和函数不兼容的情况下(即类型特征类型的检查)预先停止编译。
因此,我需要能够验证我请求的每个成员函数的返回类型,参数类型和数量,常量等。最初的问题是我用来验证匹配的较大模板的检查部分。
答案 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
内部逗号运算符左侧的时髦表达式。
子表达式&U::foo
格式不正确,可能会在
U
不是类类型,或U::foo
无法访问,或U::foo
结果成员指针不能是static_cast
到目标类型
请注意,当&U::foo
本身不明确时,查找U::foo
并不会失败。在13.4
下的C ++标准中列出的某些上下文中保证了这一点(重载函数的地址,[over.over] )。一个这样的上下文是显式类型转换(在这种情况下为static_cast
)。
该表达式还利用T B::*
可转换为T D::*
的事实,其中D
是从B
派生的类(但不是相反)。这样就不需要像iavr's answer中那样推断出类类型。
value
或value
的{{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违规行为,但重复这个名称从根本上说并不比从名称中单独指定类型更糟糕。