【问题标题】:Disable class template member for void types?禁用 void 类型的类模板成员?
【发布时间】:2021-12-31 12:12:37
【问题描述】:

考虑以下基本类模板:

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

我正在使用 &lt;type_traits&gt; 来获得一个简单的 SFINAE 实现,如果模板参数为 void,它会隐藏 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()

【问题讨论】:

  • 问题不是函数声明,而是成员变量T。在 C++ 中,变量不能具有 void 类型。
  • 你想达到什么目的?
  • Tvoid 时,您不能拥有T obj,因此禁用get 对您没有任何好处。
  • @Elliott 同意!
  • cannot reproduce 你的错误解决方法。

标签: c++ templates void sfinae


【解决方案1】:

解决此问题的一种简单方法(甚至符合 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();
}

Live demo.

【讨论】:

  • 如果它是一个大类,你仍然可以在一个单独的类中编写通用的东西,并为这个类继承它。
【解决方案2】:

第一个问题是您尝试使用

定义一个变量
T obj;

其中 T 是 void。但是根据documentation

以下任何上下文都要求类型 T 是完整的:

T类型对象的定义;

但是由于void不完整类型,你会得到错误:

错误:'A::obj' 的类型不完整

第二我们也不能有 void&amp; 的返回类型,这就是您收到您提到的错误的原因:

错误:形成对 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

【讨论】:

  • 使用指针作为成员似乎可以避免这个问题(检查问题编辑),编译器仍然抱怨get() 声明。
  • @joaocandre 在我的回答中阅读 second 原因。您仍然有 void&amp; 作为返回类型,这就是您得到相同错误的原因。
  • 我的意思是——模板声明(带有enable_if)不应该有效地禁用get() 声明吗?我知道这是一个无效的语句,因此我希望编译器忽略它。
猜你喜欢
  • 2021-07-25
  • 1970-01-01
  • 2012-05-25
  • 1970-01-01
  • 2014-02-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多