禁用 void 类型的类模板成员?

问题描述 投票:0回答:3

考虑以下基本类模板:

#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++ templates void sfinae
3个回答
3
投票

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

现场演示


1
投票

第一个问题是您尝试使用

定义变量
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


0
投票

您不需要指针解决方法。只需包装类型

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
分配哪怕一个字节。

© www.soinside.com 2019 - 2024. All rights reserved.