下面是一个简短,整洁和记录的方式做你正在尝试, 以及一些可能的错误之后解决。
#include <type_traits>
/*
Template `has_mf_foo_accepts_int_returns_int<T>`
has a static boolean public member `value` that == true
if and only if `T` is a class type that has a public
member function or member function overload
`int T::foo(ArgType) [const]` where `ArgType`
is a type to which `int` is implicitly convertible.
*/
template <typename T>
struct has_mf_foo_accepts_int_returns_int {
/* SFINAE success:
We know now here `int *` is convertible to
"pointer to return-type of T::foo(0)"
*/
template<typename A>
static constexpr bool test(
decltype(std::declval<A>().foo(0)) *prt) {
/* Yes, but is the return-type of `T::foo(0)`
actually *the same* as `int`?...
*/
return std::is_same<int *,decltype(prt)>::value;
}
// SFINAE failure :(
template <typename A>
static constexpr bool test(...) {
return false;
}
/* SFINAE probe.
Can we convert `(int *)nullptr to
"pointer to the return type of T::foo(0)"?
*/
static const bool value = test<T>(static_cast<int *>(nullptr));
};
template<typename T>
struct mystruct
{
using has_good_foo = has_mf_foo_accepts_int_returns_int<T>;
T val;
/* SFINAE:
`template<typename R> R someMethod(R)` will be this if and only
if `R` == `int` and `has_good_foo` == true.
*/
template<typename R = int>
typename std::enable_if<
(has_good_foo::value && std::is_same<R,int>::value),R
>::type
someMethod(R i) {
return val.foo(i);
}
/* SFINAE:
`template<typename R> R someMethod(R)` will be this if and only
if `R` != `int` or `has_good_foo` != true.
*/
template<typename R = int>
typename std::enable_if<
!(has_good_foo::value && std::is_same<R,int>::value),R
>::type
someMethod(R i) {
static_assert(has_good_foo::value && std::is_same<R,int>::value,
"mystruct<T> does not implement someMethod(R)");
return i;
}
};
// Testing...
#include <iostream>
struct with_foo_int
{
int foo(int i) {
return i + 1;
}
};
using namespace std;
int main(void)
{
mystruct<with_foo_int> ms1;
cout << ms1.someMethod(41) << endl;
mystruct<double> ms2;
cout << ms2.someMethod(41) << endl; // static_assert failure
return 0;
}
该解决方案忠实地再现了几个可能的漏洞,你 自己尝试张贴: -
1)它看起来好像你可能会认为评估std::declval<U>().foo(0)
是 确定的SFINAE方式是否存在U::foo
并且采用int
类型的单个参数 。它没有。它仅仅是一种确定 U::foo(ArgType)
是否存在的SFINAE方式,其中ArgType
是0
是 可以隐式转换的任何东西。因此ArgType
可以是任何指针或算术 类型,而不仅仅是int
。
2)您可能没有考虑到std::declval<U>().foo(0)
会满意 如果或任两者U::foo(ArgType)
U::foo(ArgType) const
存在。您 可能会在意您是否拨打const
或U
的非const
成员函数,并且您肯定会关心您调用的两个成员函数中的哪一个。如果 with_foo_int
被定义为:
struct with_foo_int
{
int foo(int i) const {
return i + 1;
}
int foo(int i) {
return i + 2;
}
};
然后给出的解决方案将调用非const
过载和 ms1.someMethod(41)
将== 43
。
2)很容易处理。如果您希望确保只能拨打 T::foo(ArgType) const
,请将const
限定符添加到mystruct::someMethod
。 如果您不在意或只希望拨打T::foo(ArgType)
,请保留 原样。
1)是有点难以解决,因为你必须制作一个SNIFAE探头 T::foo
这是唯一满意的,如果它有正确的签名,那 签名要么是const
合格与否。假设你想要 int T::foo(int) const
。在这种情况下,更换模板 has_mf_foo_accepts_int_returns_int
:
/* Template `has_mf_foo_arg_int_returns_int<T>
has a static boolean public member `value` that == true
if and only if `T` is a class type that has an un-overloaded
a public member `int T::foo(int) const`.
*/
template< typename T>
struct has_mf_foo_arg_int_returns_int
{
/* SFINAE foo-has-correct-sig :) */
template<typename A>
static std::true_type test(int (A::*)(int) const) {
return std::true_type();
}
/* SFINAE foo-exists :) */
template <typename A>
static decltype(test(&A::foo))
test(decltype(&A::foo),void *) {
/* foo exists. What about sig? */
typedef decltype(test(&A::foo)) return_type;
return return_type();
}
/* SFINAE game over :(*/
template<typename A>
static std::false_type test(...) {
return std::false_type();
}
/* This will be either `std::true_type` or `std::false_type` */
typedef decltype(test<T>(0,0)) type;
static const bool value = type::value; /* Which is it? */
};
,并在模板mystruct
取代:
using has_good_foo = has_mf_foo_accepts_int_returns_int<T>;
有:
using has_good_foo = has_mf_foo_arg_int_returns_int<T>;
(模板has_mf_foo_arg_int_returns_int
适用 从my other answer和 你可以阅读它的工作原理。)
你从SFINAE精度中获得的后一种方法的价格是 。该方法要求您尝试取T::foo
, 的地址以查看它是否存在。但C++不会给你超载的成员函数的地址,所以如果T::foo
被重载,这种方法将失败。
此处的代码将编译(或适当地static_assert
)与 GCC> = 4.7.2 clang> = 3.2。
该函数本身需要将相关参数作为模板参数,否则在调用点没有替换,并且您没有获得SFINAE。一个流行的解决方法是像'template' –
Xeo
它可行!我不确定为什么,但如果您将此作为答案并解释,我会很乐意接受。 – Dan
对于SFINAE,编译器(或用户)需要推导(提供)编译器稍后将尝试的类型** S ** ubstitute和** F ** ail –