为 void 类型禁用 class 模板成员?

Disable class template member for void types?

考虑以下基本 class 模板:

#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,我不确定原因是什么。 class 声明语法有什么问题吗?

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

第一个问题是您试图使用

定义一个变量
T obj;

其中 T 是 void。但是根据documentation

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

definition of an object of type T;

但由于 void 不完整类型 ,您得到错误:

error: 'A::obj' has incomplete type

第二个 我们也不能有 return 类型的 void& 这就是为什么你会得到你提到的错误:

error: forming reference to 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

解决这个问题的一个简单方法(甚至与 C++98 兼容)是编写 A 的特化。它很大 class,但它可能很烦人。

另一种方法是强制 objvoid 以外的其他类型。

#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.