考虑以下基本类模板:
#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();
};
我使用
<type_traits>
来获得一个简单的 SFINAE 实现,如果模板参数为空,则隐藏 get()
。
但是,我仍然收到 void 类型的编译器错误
error: forming reference to void
,我不确定原因是什么。类声明语法有问题吗?
int main(int argc, char const *argv[]) {
A<int> b;
A<void> t; // error: forming reference to void
return 0;
}
编辑:有人指出问题是
obj
不能为空。一种解决方法是使用指针:
#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; };
};
但我仍然得到
error: forming reference to void
,这意味着编译器仍在尝试编译get()
。
解决此问题的一种简单方法(甚至符合 C++98)是编写
A
的特化。虽然课程很大,但可能会很烦人。
另一种方法是强制
obj
为 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();
}
现场演示。
第一个问题是您尝试使用
定义变量T obj;
其中 T 是
void
。但根据文档
以下任何上下文都需要类型 T 才能完成:
T类型对象的定义;
但是由于
void
是 不完整类型,你会得到错误:
错误:'A::obj'的类型不完整
第二我们也不能有
void&
的返回类型,这就是为什么你会收到你提到的错误:
错误:形成对 void 的引用
您可以通过以下方式解决:
#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;
}
另外,不要忘记在析构函数中使用
delete
。
您不需要指针解决方法。只需包装类型
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; }
};
std::monostate
和[[no_unique_address]]
的组合告诉编译器,当data
时,不需要为T==void
分配哪怕一个字节。