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

发布于 2024-11-28 13:51:15 字数 1388 浏览 1 评论 0原文

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

I am trying to get a simple example to work to understand how to use std::enable_if. After I read this answer, I thought it shouldn't be too hard to come up with a simple example. I want to use std::enable_if to choose between two member-functions and allow only one of them to be used.

Unfortunately, the following doesn't compile with gcc 4.7 and after hours and hours of trying I am asking you guys what my mistake is.

#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 reports the following problems:

% 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()'

Why doesn't g++ delete the wrong instantiation for the second member function? According to the standard, std::enable_if< bool, T = void >::type only exists when the boolean template parameter is true. But why doesn't g++ consider this as SFINAE? I think that the overloading error message comes from the problem that g++ doesn't delete the second member function and believes that this should be an overload.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

时光沙漏 2024-12-05 13:51:15

仅当模板参数的参数推导中的替换导致构造格式错误时,SFINAE 才起作用。不存在这样的替代。

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


这是因为当实例化类模板时(在其他情况下创建 Y 类型的对象时会发生这种情况),它会实例化其所有成员声明(不一定是它们的定义/主体!)。其中还有其成员模板。请注意,此时 T 是已知的,并且 !std::is_same!std::is_sameT 是已知的。 T, int >::value 产生 false。因此它将创建一个类 Y ,其中包含

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::type 访问不存在的类型,因此该声明是错误的 -形成。因此你的程序是无效的。

您需要使成员模板的 enable_if 依赖于成员模板本身的参数。那么声明是有效的,因为整个类型仍然是依赖的。当您尝试调用其中之一时,会发生模板参数的参数推导,并且 SFINAE 会按预期发生。请参阅这个问题以及有关如何做到这一点的相应答案。

SFINAE only works if substitution in argument deduction of a template argument makes the construct ill-formed. There is no such substitution.

I thought of that too and tried to use std::is_same< T, int >::value and ! std::is_same< T, int >::value which gives the same result.

That's because when the class template is instantiated (which happens when you create an object of type Y<int> among other cases), it instantiates all its member declarations (not necessarily their definitions/bodies!). Among them are also its member templates. Note that T is known then, and !std::is_same< T, int >::value yields false. So it will create a class Y<int> which contains

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

The std::enable_if<false>::type accesses a non-existing type, so that declaration is ill-formed. And thus your program is invalid.

You need to make the member templates' enable_if depend on a parameter of the member template itself. Then the declarations are valid, because the whole type is still dependent. When you try to call one of them, argument deduction for their template arguments happen and SFINAE happens as expected. See this question and the corresponding answer on how to do that.

肩上的翅膀 2024-12-05 13:51:15

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

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

如果您想让我详细说明,请评论。我认为代码或多或少是不言自明的,但我再次制作了它,所以我可能是错的:)

你可以看到它的实际效果

I made this short example which also works.

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

Comment if you want me to elaborate. I think the code is more or less self-explanatory, but then again I made it so I might be wrong :)

You can see it in action here.

甜柠檬 2024-12-05 13:51:15

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

#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

For those late-comers that are looking for a solution that "just works":

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

Compile with:

g++ -std=gnu++14 test.cpp 

Running gives:

./a.out 
11
诗化ㄋ丶相逢 2024-12-05 13:51:15

来自这篇帖子:

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

,但可以执行以下操作:

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

From this post:

Default template arguments are not part of the signature of a template

But one can do something like 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
}
初与友歌 2024-12-05 13:51:15

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

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

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

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

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

解决方法:使用 if constexpr

// This can only be used to select an implementation, not to make foo()
// disappear entirely.
T foo() {
    if constexpr (some_condition<T>) {
        // If this part more complex, you can put it in a private member function.
        return 10;
    }
    else {
        return 5;
    }
}

正确使用 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;
}

std::enable_if 的替代样式

// do SFINAE in trailing return type
template <typename U = T>
auto foo()
  -> typename std::enable_if<some_condition<U>, T>::type {
    return 10;
}

// do SFINAE in regular return type
template <typename U = T>
typename std::enable_if<!some_condition<U>, T>::type
foo() {
    return 5;
}

混合

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

The issue is that both declarations of foo() differ in nothing but the default template arguments, and that doesn't make them separate declarations.
Defining the second foo() is just re-defining the first foo().

Here is a summary of options, from most modern to least modern:

Conditionally compile member function with requires clause

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

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

Workaround: select implementation with if constexpr

// This can only be used to select an implementation, not to make foo()
// disappear entirely.
T foo() {
    if constexpr (some_condition<T>) {
        // If this part more complex, you can put it in a private member function.
        return 10;
    }
    else {
        return 5;
    }
}

Proper use of 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;
}

Alternative style for std::enable_if

// do SFINAE in trailing return type
template <typename U = T>
auto foo()
  -> typename std::enable_if<some_condition<U>, T>::type {
    return 10;
}

// do SFINAE in regular return type
template <typename U = T>
typename std::enable_if<!some_condition<U>, T>::type
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>> {};
随风而去 2024-12-05 13:51:15

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

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

One way to solve this problem, specialization of member functions is to put the specialization into another class, then inherit from that class. You may have to change the order of inheritence to get access to all of the other underlying data but this technique does work.

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

The disadvantage of this technique is that if you need to test a lot of different things for different member functions you'll have to make a class for each one, and chain it in the inheritence tree. This is true for accessing common data members.

Ex:

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.
};
甜嗑 2024-12-05 13:51:15

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

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来自 Tick 库:

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

The boolean needs to depend on the template parameter being deduced. So an easy way to fix is to use a default boolean parameter:

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

};

However, this won't work if you want to overload the member function. Instead, its best to use TICK_MEMBER_REQUIRES from the Tick library:

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

};

You can also implement your own member requires macro like this(just in case you don't want to use another library):

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
意中人 2024-12-05 13:51:15

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

Here is my minimalist example, using a macro.
Use double brackets enable_if((...)) when using more complex expressions.

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();
}
所谓喜欢 2024-12-05 13:51:15
// 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;
}
// 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;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文