ChatGPT解决这个技术问题 Extra ChatGPT

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

我试图通过一个简单的示例来了解如何使用 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++没有删除第二个成员函数的问题,认为这应该是一个重载。

我不确定,但我认为它是以下内容: enable_if 基于 SFINAE (替换失败不是错误)。但是,这里没有任何替换,因为没有参数不能用于确定要使用的重载。你应该让“真”和“假”依赖于T。(我知道你不想在简单的例子中这样做,但现在可能太简单了......)
我也想到了这一点,并尝试使用 std::is_same< T, int >::value! std::is_same< T, int >::value 给出相同的结果。

C
Community

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 以及有关如何执行此操作的相应答案。


...只是为了澄清一下,以防万一:实例化 Y 模板类的实例时,编译器实际上不会编译模板成员函数;但是,编译器会将 T 替换为成员模板 DECLARATIONS,以便以后可以实例化这些成员模板。这个故障点不是 SFINAE,因为 SFINAE 仅适用于确定重载解析的可能函数集,并且实例化类不是确定重载解析的一组函数的情况。 (或者我认为!)
j
jpihl

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

#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 中看到它。


这不能在 VS2012 上编译。 error C4519: default template arguments are only allowed on a class template
那真不幸。我只用 gcc 对其进行了测试。也许这有帮助:stackoverflow.com/a/17543296/660982
为什么需要创建另一个模板类 Q,即使它等于 T
因为您需要模板化 test 成员函数。两者不能同时存在。 Q 只是转发类模板类型 T。您可以像这样删除类模板 Tcpp.sh/4nxw 但这有点违背了目的。
如果你被 C++ 卡住了 < 11(在我的例子中,Eigen 核心库开发),你也不能使用默认模板参数(如在 VS2012 中)。有一个解决方法。省略默认模板参数,而是将 Q* 参数添加到您的函数特化中。然后创建一个调用这些适配函数的新函数,向它们传递一个 Q* 类型的附加参数,例如 (Q*)NULL。在此处查看:cpp.sh/3d6uj(不要忘记检查 C++98 编译器选项)。
u
user1284631

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

#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
因为不是每个人都可以使用 C++17,原因可能千差万别。
无论如何,这个答案是否与 the answer above 相同。 (有关可能违反标准的情况,另请参阅下面的评论)
J
Janek Olszak

来自 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
}

它可以工作,但这基本上是模板函数,而不是类本身......它也不允许删除两个相同原型的函数之一(当您需要传递重载时)。不过,这个想法很好。您能否以工作形式重写 OP 示例,好吗?
这对我来说失败了;无论如何,在某些地方(根据链接的答案)使用 typename 而不是 class 有效。 (除非你使用一些不寻常的编译器版本?)
l
leemes

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

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

P
Paul Fultz II

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

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

那样对我不起作用。也许缺少什么?您能否以工作形式重写 OP 示例,好吗?
原始示例不适用于重载。我更新了我的答案,你可以如何通过重载来做到这一点。
A
Aedoro

这是我使用宏的简约示例。使用更复杂的表达式时使用双括号 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>
@Ben 你是说template <bool b, std::enable_if_t<b, int> = 0>吗?我认为这些天使用“requires”会更好,并且可以摆脱像这样的愚蠢宏。
大概。 C++20 概念还不够快。 :-/
Z
Zhi
// 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;
}

正如目前所写的那样,您的答案尚不清楚。请edit添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。您可以找到有关如何写出好答案的更多信息in the help center