Search code examples
c++templatesvoidsfinae

Disable class template member for void types?


Considering the following basic class template:

#include <type_traits>

template < typename T >
class A {
 public:
    A() = default;

    T obj;

    template < typename U = T, typename = typename std::enable_if< !std::is_void< U >::value >::type >
    T& get();
};

I'm using <type_traits> to get a simple SFINAE implementation that hides get() if template argument is void.

However, I still get the compiler error for void types error: forming reference to void, which I am not sure the reasons for are. Is there anything wrong with the class declaration syntax?

int main(int argc, char const *argv[]) {

    A<int> b;
    A<void> t;  // error: forming reference to void

    return 0;
}

EDIT: It was pointed out that the issue was that obj can't be void. One workaround would be to use a pointer instead:

#include <type_traits>
#include <memory>

template < typename T >
class A {
 public:
    A() = default;

    std::shared_ptr< T > obj;

    template < typename U = T, typename = typename std::enable_if< !std::is_void< U >::value >::type >
    U& get() { return *obj; };
};

But I still get the error: forming reference to void, which implies that the compiler is still trying to compile get().


Solution

  • The first problem is that you are trying to define a variable using

    T obj;
    

    where T is void. But according to documentation

    Any of the following contexts requires type T to be complete:

    definition of an object of type T;

    But since void is incomplete type, you get the error:

    error: 'A::obj' has incomplete type

    Second we also can't have a return type of void& which is why you get the error you mentioned:

    error: forming reference to void

    You can solve it by:

    #include <type_traits>
    #include <iostream>
    template < typename T >
    class A {
     public:
        A() = default;
    
        T *obj = nullptr;//note a pointer
        
        template < typename U = T>
        typename std::enable_if<!std::is_same<U, void>::value,U&>::type get()
        {
            std::cout <<"called"<<std::endl;
            if(obj != nullptr)
            {
               std::cout<<"not null"<<std::endl;
                return *obj;
            }
            else
            {
                std::cout<<"null"<<std::endl;
                obj = new T{};
                return *obj;
            }
            
        }
        ~A()
        {
            std::cout<<"destructor called"<<std::endl;
            
            if(!std::is_same<T, void>::value)
            {
                std::cout<<"deleted"<<std::endl;
                delete obj;
                obj = nullptr;
            }
            else 
            {
                std::cout<<"not deleted"<<std::endl;
            }
        }
    };
    int main(int argc, char const *argv[]) {
    
        A<int> b;
        std::cout<< b.get() <<std::endl;//this will print the string "called" and the integer 0 on console
        std::cout<<"------------------"<<std::endl;
        int &p = b.get();
        std::cout<<"------------------"<<std::endl;
        p = 32;
        std::cout << b.get()<<std::endl;
        std::cout<<"------------------"<<std::endl;
        
        A<void> t;
       
        return 0;
    }
    

    Also, don't forget to use delete in the destructor.