std :: enable_if有条件地编译一个成员函数
我试图得到一个简单的例子来了解如何使用std::enable_if
。 读完这个答案之后 ,我想应该不难想出一个简单的例子。 我想使用std::enable_if
来select两个成员函数,并只允许使用其中的一个。
不幸的是,下面的代码不能用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> TY::foo()' cannot be overloaded enable_if.cpp:9:15: error: with `template<class T> template<class> TY::foo()'
g ++为什么不删除第二个成员函数的错误实例化? 根据标准,当布尔模板参数为true时, std::enable_if< bool, T = void >::type
才存在。 但为什么不g ++认为这是SFINAE? 我认为重载错误消息来自g ++不删除第二个成员函数,并认为这应该是一个重载的问题。
SFINAE只有在模板参数的参数推导中的replace使得构造不合格时才起作用。 没有这样的替代。
我也想到了这一点,并试图使用
std::is_same< T, int >::value
和! std::is_same< T, int >::value
! std::is_same< T, int >::value
给出相同的结果。
这是因为当类模板被实例化时(在其他情况下创buildtypes为Y<int>
的对象时),它将实例化其所有成员声明(不一定是它们的定义/正文!)。 其中也是它的成员模板。 请注意, T
是已知的,而!std::is_same< T, int >::value
产生false。 所以它会创build一个包含的类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
访问一个不存在的types,所以声明是格式不正确的。 因此你的程序是无效的。
您需要使成员模板的enable_if
取决于成员模板本身的参数。 那么声明是有效的,因为整个types依然是依赖的。 当您尝试调用其中一个参数时,会发生对其模板参数的参数推导,并且按照预期发生SFINAE。 看到这个问题和相应的答案如何做到这一点。
我做了这个简短的例子,也是有效的。
#include <iostream> #include <type_traits> class foo; class bar; template<class T> struct check { template<class Q = T> typename std::enable_if<std::is_same<Q, bar>::value, bool>::type test() { return true; } template<class Q = T> typename std::enable_if<!std::is_same<Q, bar>::value, bool>::type test() { return false; } }; int main() { check<foo> check_foo; check<bar> check_bar; if (!check_foo.test() && check_bar.test()) std::cout << "It works!" << std::endl; return 0; }
评论,如果你想我详细说明。 我认为这个代码或多或less都是不言而喻的,但是我又这样做了,所以我可能会错的:)
你可以在这里看到它的行动。
对于那些正在寻找“正常工作”解决scheme的后来者来说:
#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
从这个职位:
默认模板参数不是模板签名的一部分
但是可以这样做:
#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 }
解决这个问题的一种方法,成员函数的专业化就是把专业化放到另一个类中,然后inheritance这个类。 您可能必须更改inheritance的顺序才能访问所有其他基础数据,但这种方法确实有效。
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. };
这种技术的缺点是,如果你需要为不同的成员函数testing很多不同的东西,你必须为每一个成员创build一个类,并将其链接到inheritance树中。 访问常用数据成员是这样的。
例如:
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; } };
你也可以像这样实现自己的成员需要macros(以防万一你不想使用另一个库):
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