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