我正在尝试编写一个模板类,它可能会也可能不会定义特定的成员函数,具体取决于其模板参数类型。此成员函数的返回类型取决于模板参数成员的返回类型(如果已定义)。
以下是我的代码的最小示例
#include <iostream>
#include <type_traits>
template <typename T>
struct has_foo_int {
private:
template <typename U>
static decltype(std::declval<U>().foo(0), void(), std::true_type()) test(int);
template <typename>
static std::false_type test(...);
public:
typedef decltype(test<T>(0)) test_type;
enum { value = test_type::value };
};
template <typename T, bool HasFooInt>
struct foo_int_return_type;
template<typename T>
struct foo_int_return_type<T,false> {};
template<typename T>
struct foo_int_return_type<T,true> {
using type = decltype(std::declval<T>().foo(0));
};
template<typename T>
struct mystruct
{
T val;
//auto someMethod(int i) -> decltype(std::declval<T>().foo(0)) // error: request for member ‘foo’ in ‘std::declval<double>()’, which is of non-class type ‘double’
//auto someMethod(int i) -> typename foo_int_return_type<T,has_foo_int<T>::value>::type // error: no type named ‘type’ in ‘struct foo_int_return_type<double, false>’
template<typename R=typename foo_int_return_type<T,has_foo_int<T>::value>::type> R someMethod(int i) // error: no type named ‘type’ in ‘struct foo_int_return_type<double, false>’
{
return val.foo(i);
}
};
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;
return 0;
}
我想要发生的是代码编译正常并输出42 ms1.someFunc(41)
。如果一个人意外地试图在someFunc
上调用ms2
,那么我也会发现它无法编译。
不幸的是,我尝试过的每一种选择都失败了。第一和第二,我想我明白为什么他们不会工作。
我读到here SFINAE仅适用于模板函数,所以我尝试给出一个虚拟模板参数来计算返回类型,但这也失败了。
我显然不明白这里有什么,我错过了什么?是否有可能实现我想要做的事情?
感谢。
P.S。我正在使用g ++ 4.7.3
P.p.s我也尝试了std::enable_if
,但得到的结果与我的foo_int_return_type
结构大致相同。
答案 0 :(得分:2)
这是一个简短,整洁且有记录的方式来做你正在尝试的事情, 之后解决了一些可能的错误。
#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
。它没有。它只是一种确定是否是SFINAE的方式
U::foo(ArgType)
存在ArgType
,其中0
是ArgType
隐含可兑换。因此int
可以是任何指针或算术
类型,而不仅仅是std::declval<U>().foo(0)
。
2)您可能没有考虑过U::foo(ArgType)
会满意
如果存在U::foo(ArgType) const
const
的或两者。您
您可能会关注是否要调用const
或非U
成员函数
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
会== T::foo(ArgType) const
。
2)容易处理。如果您希望确保只能打电话
const
然后将mystruct::someMethod
限定符添加到T::foo(ArgType)
。
如果你不在乎或只想打电话给T::foo
,那就留下一些东西
他们是。
1)有点难以解决,因为你必须制作一个SNIFAE探测器
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? */
};
with:
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
(模板T::foo
已经过调整
来自my other answer和
你可以在那里阅读它的工作原理。)
从后一种方法中获得的SFINAE精度得到的是
一个价格。该方法要求您尝试获取T::foo
的地址,
看它是否存在。但是C ++不会给你一个重载的地址
成员函数,因此如果static_assert
重载,此方法将失败。
这里的代码将编译(或适当地{{1}}) GCC&gt; = 4.7.2 clang&gt; = 3.2。