我试图通过一个简单的示例来了解如何使用 std::enable_if
。阅读 this answer 后,我认为想出一个简单的例子应该不会太难。我想使用 std::enable_if
在两个成员函数之间进行选择,并且只允许使用其中一个。
不幸的是,以下内容无法使用 gcc 4.7 编译,经过数小时的尝试,我问你们我的错误是什么。
#include <utility>
#include <iostream>
template< class T >
class Y {
public:
template < typename = typename std::enable_if< true >::type >
T foo() {
return 10;
}
template < typename = typename std::enable_if< false >::type >
T foo() {
return 10;
}
};
int main() {
Y< double > y;
std::cout << y.foo() << std::endl;
}
gcc 报告以下问题:
% LANG=C make CXXFLAGS="-std=c++0x" enable_if
g++ -std=c++0x enable_if.cpp -o enable_if
enable_if.cpp:12:65: error: `type' in `struct std::enable_if<false>' does not name a type
enable_if.cpp:13:15: error: `template<class T> template<class> T Y::foo()' cannot be overloaded
enable_if.cpp:9:15: error: with `template<class T> template<class> T Y::foo()'
为什么 g++ 不删除第二个成员函数的错误实例化?根据标准,std::enable_if< bool, T = void >::type
仅在布尔模板参数为 true 时存在。但是为什么 g++ 不认为这是 SFINAE 呢?我认为重载错误信息来自g++没有删除第二个成员函数的问题,认为这应该是一个重载。
std::is_same< T, int >::value
和 ! std::is_same< T, int >::value
给出相同的结果。
SFINAE 仅在模板参数的参数推导中的替换使构造格式不正确时才有效。没有这样的替代品。
我也想到了这一点,并尝试使用 std::is_same< T, int >::value 和 ! std::is_same< T, int >::value 给出相同的结果。
这是因为当类模板被实例化时(当您创建类型为 Y<int>
的对象以及其他情况时会发生这种情况),它会实例化其所有成员声明(不一定是它们的定义/主体!)。其中还有它的成员模板。请注意,此时 T
是已知的,并且 !std::is_same< T, int >::value
产生错误。因此它将创建一个类 Y<int>
,其中包含
class Y<int> {
public:
/* instantiated from
template < typename = typename std::enable_if<
std::is_same< T, int >::value >::type >
T foo() {
return 10;
}
*/
template < typename = typename std::enable_if< true >::type >
int foo();
/* instantiated from
template < typename = typename std::enable_if<
! std::is_same< T, int >::value >::type >
T foo() {
return 10;
}
*/
template < typename = typename std::enable_if< false >::type >
int foo();
};
std::enable_if<false>::type
访问不存在的类型,因此该声明格式不正确。因此您的程序无效。
您需要使成员模板的enable_if
依赖于成员模板本身的参数。那么声明是有效的,因为整个类型仍然是依赖的。当您尝试调用其中一个时,会发生对其模板参数的参数推导,并且 SFINAE 会按预期发生。请参阅 this question 以及有关如何执行此操作的相应答案。
我做了这个简短的例子,它也有效。
#include <iostream>
#include <type_traits>
class foo;
class bar;
template<class T>
struct is_bar
{
template<class Q = T>
typename std::enable_if<std::is_same<Q, bar>::value, bool>::type check()
{
return true;
}
template<class Q = T>
typename std::enable_if<!std::is_same<Q, bar>::value, bool>::type check()
{
return false;
}
};
int main()
{
is_bar<foo> foo_is_bar;
is_bar<bar> bar_is_bar;
if (!foo_is_bar.check() && bar_is_bar.check())
std::cout << "It works!" << std::endl;
return 0;
}
如果您想让我详细说明,请发表评论。我认为代码或多或少是不言自明的,但我又做了一次,所以我可能错了:)
您可以在操作 here 中看到它。
error C4519: default template arguments are only allowed on a class template
。
Q
,即使它等于 T
?
Q*
参数添加到您的函数特化中。然后创建一个调用这些适配函数的新函数,向它们传递一个 Q*
类型的附加参数,例如 (Q*)NULL
。在此处查看:cpp.sh/3d6uj(不要忘记检查 C++98 编译器选项)。
对于那些正在寻找“行之有效”的解决方案的后来者:
#include <utility>
#include <iostream>
template< typename T >
class Y {
template< bool cond, typename U >
using resolvedType = typename std::enable_if< cond, U >::type;
public:
template< typename U = T >
resolvedType< true, U > foo() {
return 11;
}
template< typename U = T >
resolvedType< false, U > foo() {
return 12;
}
};
int main() {
Y< double > y;
std::cout << y.foo() << std::endl;
}
编译:
g++ -std=gnu++14 test.cpp
运行给出:
./a.out
11
std::enable_if_t
重命名为 resolvedType
。
来自 this 帖子:
默认模板参数不是模板签名的一部分
但是可以做这样的事情:
#include <iostream>
struct Foo {
template < class T,
class std::enable_if < !std::is_integral<T>::value, int >::type = 0 >
void f(const T& value)
{
std::cout << "Not int" << std::endl;
}
template<class T,
class std::enable_if<std::is_integral<T>::value, int>::type = 0>
void f(const T& value)
{
std::cout << "Int" << std::endl;
}
};
int main()
{
Foo foo;
foo.f(1);
foo.f(1.1);
// Output:
// Int
// Not int
}
typename
而不是 class
有效。 (除非你使用一些不寻常的编译器版本?)
解决此问题的一种方法,成员函数的特化是将特化放入另一个类,然后从该类继承。您可能必须更改继承顺序才能访问所有其他基础数据,但这种技术确实有效。
template< class T, bool condition> struct FooImpl;
template<class T> struct FooImpl<T, true> {
T foo() { return 10; }
};
template<class T> struct FoolImpl<T,false> {
T foo() { return 5; }
};
template< class T >
class Y : public FooImpl<T, boost::is_integer<T> > // whatever your test is goes here.
{
public:
typedef FooImpl<T, boost::is_integer<T> > inherited;
// you will need to use "inherited::" if you want to name any of the
// members of those inherited classes.
};
这种技术的缺点是,如果你需要为不同的成员函数测试很多不同的东西,你必须为每个成员创建一个类,并将其链接到继承树中。这适用于访问公共数据成员。
前任:
template<class T, bool condition> class Goo;
// repeat pattern above.
template<class T, bool condition>
class Foo<T, true> : public Goo<T, boost::test<T> > {
public:
typedef Goo<T, boost::test<T> > inherited:
// etc. etc.
};
布尔值需要依赖于推导的模板参数。因此,一种简单的修复方法是使用默认的布尔参数:
template< class T >
class Y {
public:
template < bool EnableBool = true, typename = typename std::enable_if<( std::is_same<T, double>::value && EnableBool )>::type >
T foo() {
return 10;
}
};
但是,如果您想重载成员函数,这将不起作用。相反,最好使用 Tick 库中的 TICK_MEMBER_REQUIRES
:
template< class T >
class Y {
public:
TICK_MEMBER_REQUIRES(std::is_same<T, double>::value)
T foo() {
return 10;
}
TICK_MEMBER_REQUIRES(!std::is_same<T, double>::value)
T foo() {
return 10;
}
};
您还可以实现自己的成员需要这样的宏(以防万一您不想使用另一个库):
template<long N>
struct requires_enum
{
enum class type
{
none,
all
};
};
#define MEMBER_REQUIRES(...) \
typename requires_enum<__LINE__>::type PrivateRequiresEnum ## __LINE__ = requires_enum<__LINE__>::type::none, \
class=typename std::enable_if<((PrivateRequiresEnum ## __LINE__ == requires_enum<__LINE__>::type::none) && (__VA_ARGS__))>::type
这是我使用宏的简约示例。使用更复杂的表达式时使用双括号 enable_if((...))
。
template<bool b, std::enable_if_t<b, int> = 0>
using helper_enable_if = int;
#define enable_if(value) typename = helper_enable_if<value>
struct Test
{
template<enable_if(false)>
void run();
}
template <typename = std::enable_if_t<b, int> = 0>
template <bool b, std::enable_if_t<b, int> = 0>
吗?我认为这些天使用“requires”会更好,并且可以摆脱像这样的愚蠢宏。
// Try this one:
#include <iostream>
#include <type_traits>
// suppose you want to disable certain member functions based on the tag
struct FooTag;
struct BarTag;
// macro to save some typings in the following
// note that a dummy typename is involved in both the
// first and second parameters.
// this should be different than the template parameter of the class (typename T for Widget below)
#define EnableIfFoo(T) \
template <typename Dummy = void, typename = \
typename std::enable_if<std::is_same<FooTag, T>::value, Dummy>::type>
#define EnableIfBar(T) \
template <typename Dummy = void, typename = \
typename std::enable_if<std::is_same<BarTag, T>::value, Dummy>::type>
template <typename T>
class Widget {
public:
// enable this function only if the tag is Bar
EnableIfFoo(T)
void print() const { std::cout << "I am a Foo!" << std::endl; }
// enable this function only if the tag is Foo
EnableIfBar(T)
void display() const { std::cout << "I am a Bar!" << std::endl; }
};
int main() {
// instantiate a widget with tag Foo
// only print is enabled; display is not
Widget<FooTag> fw;
fw.print();
//fw.display(); // compile error !!
// instantiate a Widget using tag Bar
// only display is enabled; print is not
Widget<BarTag> bw;
bw.display();
//bw.print(); // compile error !!
return 0;
}
不定期副业成功案例分享
Y
模板类的实例时,编译器实际上不会编译模板成员函数;但是,编译器会将T
替换为成员模板 DECLARATIONS,以便以后可以实例化这些成员模板。这个故障点不是 SFINAE,因为 SFINAE 仅适用于确定重载解析的可能函数集,并且实例化类不是确定重载解析的一组函数的情况。 (或者我认为!)