Able _ if 有条件地编译一个成员函数

我试图得到一个简单的例子工作,以了解如何使用 std::enable_if。在我阅读了 这个答案之后,我认为想出一个简单的例子应该不会太难。我想使用 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只有在布尔模板参数为真时才存在。但是为什么 g + + 不认为这是 SFINAE 呢?我认为重载错误消息来自于 g + + 没有删除第二个成员函数的问题,并认为这应该是一个重载。

167433 次浏览

SFINAE 只有在模板参数的参数推导中的替换使得构造格式不正确时才有效。没有这种替代。

我也想到了这一点,并尝试使用 std::is_same< T, int >::value! std::is_same< T, int >::value,这给出了相同的结果。

这是因为在实例化类模板时(在创建类型为 Y<int>的对象和其他情况下会发生这种情况) ,它会实例化其所有成员声明(不一定是它们的定义/主体!).其中还包括其成员模板。注意,这时 T已知,而 !std::is_same< T, int >::value产生 false。因此,它将创建一个类 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 将按预期发生。请参阅 这个问题和相应的答案,了解如何做到这一点。

解决这个问题的一种方法是,将成员函数的专门化放到另一个类中,然后从该类继承。您可能必须更改继承的顺序才能访问所有其他基础数据,但是这种技术确实有效。

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.
};

我做了这个简短的例子,也工作。

#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;
}

如果你想让我详细说明,请发表评论。我认为这个代码或多或少是不言自明的,但是我做了它,所以我可能是错的:)

你可以看到它在行动 给你

布尔值需要依赖于正在推导的模板参数。因此,一个简单的修复方法是使用一个默认的布尔参数:

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_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

来自 这个的邮件:

默认模板参数不是模板签名的一部分

但人们可以这样做:

#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
}

对于那些正在寻找一种“恰到好处”的解决方案的后来者:

#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

下面是我的极简主义例子,使用一个宏。 当使用更复杂的表达式时,请使用双括号 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();
}
// 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;
}