Home > Enterprise >  Disable class template member for void types?
Disable class template member for void types?

Time:11-22

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().

CodePudding user response:

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;
        delete obj;
        obj = nullptr;
    }
};
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.

CodePudding user response:

One simple way (C 98-compliant even) of resolving this is to write a specialisation of A. It it's a big class, it could be annoying though.

Another method is to force obj be of some other type than void.

#include <type_traits>

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

    struct empty{};
    [[no_unique_address]]
    std::conditional_t<std::is_same_v<T, void>, empty, T> obj;

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

int main()
{
  A<void> v;
  A<int> x;
  int& y = x.get();
}

Live demo.

  • Related