std :: enable_if有条件地编译成员函数


156

我试图通过一个简单的示例来了解如何使用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仅当布尔模板参数为true时存在。但是g ++为什么不将其视为SFINAE?我认为重载错误消息来自g ++不会删除第二个成员函数的问题,并认为这应该是重载。


1
我不确定,但是我认为是以下几种:enable_if基于SFINAE(替代失败不是错误)。但是,这里没有任何替代方法,因为不能使用任何参数来确定要使用的重载。您应该使“ true”和“ false”取决于T。(我知道您不想在简单的示例中这样做,但是现在可能太简单了……)
Philipp

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

Answers:


117

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会按预期发生。请参阅此问题以及有关如何执行此操作的相应答案。


14
...只是为了澄清,以防万一它有用:当实例Y化模板类的实例时,编译器实际上不会编译模板成员函数;但是,编译器将执行替换T成成员模板DECLARATIONS,以便可以在以后的时间实例化这些成员模板。该故障点不是SFINAE,因为SFINAE仅在确定用于过载解析的可能函数集时才适用,并且实例化类不是为过载解析确定一组函数的情况。(或者我想!)
Dan Nissenbaum 2013年

93

我做了这个简短的例子,它也有效。

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

如果您要我详细说明,请发表评论。我认为代码或多或少是不言自明的,但是后来我又做了,所以我可能是错的:)

您可以在此处查看它的运行情况。


2
这不能在VS2012上编译。error C4519: default template arguments are only allowed on a class template
PythonNut 2014年

1
那真不幸。我只在gcc上测试过。也许有帮助:stackoverflow.com/a/17543296/660982
jpihl 2014年

1
这无疑是最好的答案,而这正是我一直在寻找的东西。
Weipeng L 2014年

3
为什么需要创建另一个模板类Q,即使它等于T
ilya1725 '17

1
因为您需要模板化test成员函数。两者不能同时存在。Q只是转发类模板类型T。您可以这样删除类模板Tcpp.sh/4nxw,但这有点违反了目的。
jpihl

13

对于那些正在寻找“可行”解决方案的后来者:

#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

6
嗯,为什么要重命名std::enable_if_tresolvedType
Qwertie

1
因为并非所有人都能出于各种原因使用C ++ 17。
杨扬

9

这篇文章:

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

但是可以做这样的事情:

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

它可以工作,但这基本上是模板函数,而不是类本身...它不允许删除两个相同原型函数之一(当您需要传递重载时)。但是,这个主意很好。您能以工作形式重写OP示例吗?
user1284631 2014年

5

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

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

4

布尔值取决于所推导的模板参数。因此,一种简单的修复方法是使用默认的布尔参数:

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_REQUIRESTick库中使用:

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

};

您还可以实现自己的成员require宏,如下所示(以防万一您不想使用其他库):

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

这样对我不起作用。Maaybe缺少什么?您能以工作形式重写OP示例吗?
user1284631 2014年

原始示例不适用于重载。我更新了答案,如何解决过载问题。
Paul Fultz II

0

这是我使用宏的简约示例。使用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();
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.