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

std::enable_if to conditionally compile a member function

提问人:evnu 提问时间:8/7/2011 最后编辑:Jan Schultkeevnu 更新时间:9/14/2023 访问量:182498

问:

我正在尝试获得一个简单的示例来了解如何使用 .看完这个答案后,我觉得想出一个简单的例子应该不会太难。我想用于在两个成员函数之间进行选择,并且只允许使用其中一个。std::enable_ifstd::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++ 不删除第二个成员函数的错误实例化?根据标准,仅当布尔模板参数为 true 时才存在。但是为什么 g++ 不认为这是 SFINAE?我认为重载错误消息来自g++不删除第二个成员函数的问题,并认为这应该是重载。std::enable_if< bool, T = void >::type

C 11 模板 G++ 启用-如果 20 17 C +98

评论

1赞 Philipp 8/7/2011
我不确定,但我认为如下:enable_if基于 SFINAE(替换失败不是错误)。但是,此处没有任何替换,因为不能使用任何参数来确定要使用的重载。你应该让“true”和“false”依赖于 T.(我知道你不想在简单的例子中这样做,但现在可能太简单了......
3赞 evnu 8/7/2011
我也想到了这一点,并尝试使用,这给出了相同的结果。std::is_same< T, int >::value! std::is_same< T, int >::value

答:

137赞 Johannes Schaub - litb 8/7/2011 #1

仅当模板参数的参数推导中的替换使构造格式不正确时,SFINAE 才有效。没有这样的替代品。

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

这是因为当类模板被实例化时(当你在其他情况下创建一个类型的对象时会发生这种情况),它会实例化其所有成员声明(不一定是它们的定义/正文!其中还有其成员模板。请注意,当时是已知的,并产生假。因此,它将创建一个包含Y<int>T!std::is_same< T, int >::valueY<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

您需要使成员模板依赖于成员模板本身的参数。然后声明是有效的,因为整个类型仍然是依赖的。当您尝试调用其中一个时,将对其模板参数进行参数推导,并且 SFINAE 按预期发生。请参阅此问题以及有关如何执行此操作的相应答案。enable_if

评论

20赞 Dan Nissenbaum 3/29/2013
...澄清一下,以防万一有用:当实例化模板类的实例时,编译器实际上不会编译模板成员函数;但是,编译器将执行 into 成员模板 DECLARATIONS 的替换,以便以后可以实例化这些成员模板。此故障点不是 SFINAE,因为 SFINAE 仅在确定用于重载解决的可能函数集时才适用,而实例化类不是确定用于重载解决的一组函数的情况。(或者我是这么认为的!YT
6赞 Gary Powell 6/15/2012 #2

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

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.
};
117赞 jpihl 1/31/2014 #3

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

#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赞 PythonNut 6/7/2014
这不会在 VS2012 上编译。.error C4519: default template arguments are only allowed on a class template
2赞 jpihl 6/10/2014
这是不幸的。我只用 gcc 测试了它。也许这会有所帮助:stackoverflow.com/a/17543296/660982
8赞 ilya1725 4/26/2017
为什么需要创建另一个模板类,即使它等于 ?QT
3赞 jpihl 4/26/2017
因为您需要对成员函数进行模板化。两者不能同时存在。 只是转发类模板类型。您可以像这样删除类模板:cpp.sh/4nxw 但这有点违背了目的。testQTT
1赞 Martin Pecka 2/2/2019
如果你坚持使用C++ < 11(就像我的例子一样,Eigen核心库开发),你也不能使用默认的模板参数(如VS2012)。有一个解决方法。省略默认模板参数,而是将参数添加到函数专用化中。然后创建一个新函数来调用这些改编的函数,并给它们传递一个额外的参数,例如 .在这里查看:cpp.sh/3d6uj(不要忘记检查 C++98 编译器选项)。Q*Q*(Q*)NULL
5赞 Paul Fultz II 8/17/2014 #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 库中的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

评论

0赞 user1284631 10/1/2014
它对我不起作用。Maaybe缺少什么?你能以工作形式重写 OP 示例吗?
0赞 Paul Fultz II 10/1/2014
原始示例不适用于重载。我更新了我的答案,你怎么能用超载来做到这一点。
12赞 Janek Olszak 8/25/2014 #5

这篇文章

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

但是可以做这样的事情:

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

评论

0赞 user1284631 10/1/2014
它有效,但这基本上是模板函数,而不是类本身......它不允许删除两个完全相同的原型函数之一(当您需要传递重载时)。但是,这个想法很好。你能以工作形式重写 OP 示例吗?
0赞 user202729 1/19/2022
这对我来说只是失败了;无论如何,在某些地方使用而不是使用(根据链接的答案)是有效的。(除非你使用的是一些不寻常的编译器版本?typenameclass
18赞 user1284631 9/30/2014 #6

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

#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

评论

8赞 Qwertie 2/9/2019
嗯,你为什么要重命名为.std::enable_if_tresolvedType
5赞 James Yang 12/12/2019
因为不是每个人都可以使用 C++17,原因可能千差万别。
0赞 user202729 1/19/2022
反正这个答案不是和上面的答案一样吗?(另请参阅下面的评论,了解可能违反标准的情况)
-1赞 Aedoro 6/22/2020 #7

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

评论

0赞 Ben 3/4/2022
我理解你为什么想要那个,但这不是惯用的。我们应该鼓励人们避免使用宏,而只写template <typename = std::enable_if_t<b, int> = 0>
0赞 Aedoro 3/7/2022
@Ben 你是说?我认为现在使用“requires”要好得多,并且摆脱了像这样的愚蠢宏。template <bool b, std::enable_if_t<b, int> = 0>
0赞 Ben 3/8/2022
可能。C++20 概念来得太快了。:-/
-1赞 Zhi 10/5/2021 #8
// 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;
}

评论

0赞 Community 10/5/2021
正如目前所写的那样,你的答案尚不清楚。请编辑以添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。您可以在帮助中心找到有关如何写出好答案的更多信息。
3赞 Jan Schultke 9/14/2023 #9

问题在于,除了默认模板参数之外,这两个声明没有任何不同之处,这并不能使它们成为单独的声明。 定义第二个只是重新定义第一个。foo()foo()foo()

以下是从最现代到最不现代的选项摘要:

有条件地编译子句 的成员函数requires

T foo() requires some_condition<T> {
    return 10;
}

// note: Trailing requires clause could also be omitted here
//       because the first function is more constrained, and overload resolution
//       will select the better one.
T foo() requires (!some_condition<T>) {
    return 5;
}

解决办法:选择具有 if constexpr 的实现

T foo() {
    if constexpr (some_condition<T>) {
        // if this is more complex, you can put it in a private member function
        return 10;
    }
    else {
        return 5;
    }
}

正确使用 c+std::enable_if

// note: Unfortunately, we pollute the function signature with additional template
//       parameters.
// note: std::enable_if_t can be used in C++14
// note: The additional U parameter is necessary to make the condition dependent
//       on a template parameter; otherwise the compiler will simply emit an error
//       for some_condition<T>.
template <typename U = T, typename std::enable_if<some_condition<U>, int>::type = 0>
T foo() {
    return 10;
}

template <typename U = T, typename std::enable_if<!some_condition<U>, int>::type = 0>
T foo() {
    return 5;
}

Mixins

template <typename T, bool B>
struct mixin;

template <typename T>
struct mixin<T, true> {
    T foo() { return 10; }
}

template <typename T>
struct mixin<T, false> {
    T foo() { return 5; }
}

template <typename T>
struct Y : mixin<T, some_condition<T>> {};