joaocandre
joaocandre

Reputation: 1745

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

Upvotes: 2

Views: 1490

Answers (3)

MSalters
MSalters

Reputation: 179809

You don't need a pointer workaround. Just wrap the type T:

template <typename T> using non_void = 
    std::conditional_t<std::is_void_v<T>, std::monostate, T>;

template <typename T> 
struct A {
    [[no_unique_address]] non_void<T> data;
    auto& get() { return data; }
};

The combination of std::monostate and [[no_unique_address]] tells the compiler that there's no need to allocate even a single byte for data when T==void.

Upvotes: 1

user12002570
user12002570

Reputation: 1

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.

Upvotes: 1

n. m. could be an AI
n. m. could be an AI

Reputation: 119877

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.

Upvotes: 3

Related Questions