std::enable_if to conditionally compile a member function

前端 未结 7 2064
执笔经年
执笔经年 2020-11-22 04:57

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 wi

相关标签:
7条回答
  • 2020-11-22 05:29

    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.

    0 讨论(0)
  • 2020-11-22 05:32

    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.

    0 讨论(0)
  • 2020-11-22 05:36

    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
    
    0 讨论(0)
  • 2020-11-22 05:37

    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
    }
    
    0 讨论(0)
  • 2020-11-22 05:38

    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();
    }
    
    0 讨论(0)
  • 2020-11-22 05:44

    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.
    };
    
    0 讨论(0)
提交回复
热议问题