使用enable_if澄清成员函数模板的专业化

时间:2019-04-07 12:30:36

标签: c++ templates sfinae member-functions enable-if

我想了解我在尝试最大程度地减少成员函数模板专业化的冗长性时出错的地方。这样做时会出现编译错误。这是有效的版本,希望可以阐明我要实现的目标:

#include <iostream>
#include <type_traits>


typedef int i32;
template<class T>
struct rtvec
{
private:
    T* e;
    i32 d;

public:
    rtvec(i32 d) : d(d), e(new T[d]) {}
    //template<typename Args...>
    //rtvec()
    rtvec(const rtvec& in) : d(in.d), e(new T[in.d])
    {
        for (i32 i = 0; i < d; ++i)
            at(i) = in.at(i);
    }

    rtvec(rtvec<typename std::remove_pointer_t<T>>& in) : d(in.dim()), e(new T[in.dim()])
    {
        for (i32 i = 0; i < d; ++i)
            e[i] = &in.at(i);
    }
    ~rtvec() { delete[] e; }
    i32 dim() const { return d; }

    template<typename U=T, 
        typename std::enable_if_t<std::is_same_v<U,T>>* = nullptr,
        typename std::enable_if_t<!std::is_pointer_v<U>>* = nullptr>
    inline T& at(i32 i) 
    {
        return e[i];
    }

    template<typename U = T,
        typename std::enable_if_t<std::is_same_v<U, T>>* = nullptr,
        typename std::enable_if_t<std::is_pointer_v<U>>* = nullptr>
        inline typename std::remove_pointer_t<T>& at(i32 i)
    {
        return *e[i];
    }

};


int main()
{
    rtvec<float> v(2);
    v.at(0) = 1;
    v.at(1) = 2;
    rtvec<float*> p = v;
    p.at(0) = 5;
    std::cout << v.at(0) << " " << v.at(1) << "\n";
    return 0;
}

基本上,我试图制作一个运行时变量维向量类,当使用指针实例化该向量类时,可以用作对相同类型向量的一种引用(更确切地说,我每个坐标都有多个数组)点集,我想使用“参考”向量来处理这些点,就好像它们在内存中以其他方式排序一样。 但是,当我尝试简化代码时,尝试删除我认为不必要的typename U。我在MSVC2017中收到以下编译错误:std::enable_if_t<false,void>' : Failed to specialize alias template。这是我要实现的不太冗长的版本:

struct rtvec
{
private:
    T* e;
    i32 d;

public:
    rtvec(i32 d) : d(d), e(new T[d]) {}

    template<typename std::enable_if_t<!std::is_pointer_v<T>>* = nullptr>
    rtvec(const rtvec& in) : d(in.d), e(new T[in.d])
    {
        for (i32 i = 0; i < d; ++i)
            at(i) = in.at(i);
    }

    template<typename std::enable_if_t<std::is_pointer_v<T>>* = nullptr>
    rtvec(rtvec<typename std::remove_pointer_t<T>>& in) : d(in.dim()), e(new T[in.dim()])
    {
        for (i32 i = 0; i < d; ++i)
            e[i] = &in.at(i);
    }
    ~rtvec() { delete[] e; }
    i32 dim() const { return d; }


    template<typename std::enable_if_t<!std::is_pointer_v<T>>* = nullptr>
    inline T& at(i32 i)
    {
        return e[i];
    }

    template<typename std::enable_if_t<std::is_pointer_v<T>>* = nullptr>
    inline typename std::remove_pointer<T>::type& at(i32 i)
    {
        return *e[i];
    }

};

但是,如果我稍加修改,它就会编译:

template<class T>
struct rtvec
{
private:
    T* e;
    i32 d;

public:
    rtvec(i32 d) : d(d), e(new T[d]) {}

    template<typename std::enable_if_t<!std::is_pointer_v<T>>* = nullptr>
    rtvec(const rtvec& in) : d(in.d), e(new T[in.d])
    {
        for (i32 i = 0; i < d; ++i)
            at(i) = in.at(i);
    }

    /*template<typename std::enable_if_t<std::is_pointer_v<T>>* = nullptr>
    rtvec(rtvec<typename std::remove_pointer_t<T>>& in) : d(in.dim()), e(new T[in.dim()])
    {
        for (i32 i = 0; i < d; ++i)
            e[i] = &in.at(i);
    }*/
    ~rtvec() { delete[] e; }
    i32 dim() const { return d; }


    template<typename std::enable_if_t<!std::is_pointer_v<T>>* = nullptr>
    inline T& at(i32 i)
    {
        return e[i];
    }

    /*template<typename std::enable_if_t<std::is_pointer_v<T>>* = nullptr>
    inline typename std::remove_pointer<T>::type& at(i32 i)
    {
        return *e[i];
    }*/


};

(只要与指针相关的部分也在main中被注释掉)。我想了解是什么导致第二代码无法编译。

我不直接对类进行专业化的原因是,在我最初的实现中,我还有很多其他成员函数在两个专业化之间是等效的,我不想重复。

2 个答案:

答案 0 :(得分:3)

  

但是,当我尝试简化代码时,通过尝试删除我认为不必要的内容

很遗憾(如果我理解正确),您已经删除了必要的内容

如果我理解正确,则您简化了以下方法

template<typename U=T, 
    typename std::enable_if_t<std::is_same_v<U,T>>* = nullptr,
    typename std::enable_if_t<!std::is_pointer_v<U>>* = nullptr>
inline T& at(i32 i) 
{
    return e[i];
}

template<typename U = T,
    typename std::enable_if_t<std::is_same_v<U, T>>* = nullptr,
    typename std::enable_if_t<std::is_pointer_v<U>>* = nullptr>
    inline typename std::remove_pointer_t<T>& at(i32 i)
{
    return *e[i];
}

如下

template<typename std::enable_if_t<!std::is_pointer_v<T>>* = nullptr>
inline T& at(i32 i)
{
    return e[i];
}

template<typename std::enable_if_t<std::is_pointer_v<T>>* = nullptr>
inline typename std::remove_pointer<T>::type& at(i32 i)
{
    return *e[i];
}

不幸的是,对于模板方法,SFINAE仅在基于方法本身的模板参数使用测试(std::enable_if_t)时起作用。

我的意思是:当std::enable_if_t测试涉及T(并且仅包含T)时,SFINEE无效,因为T是该结构的模板参数,而不是该方法的模板参数。

所以您需要窍门

typename U = T

在方法的模板参数中“转换” T类型。

您可以简化删除typename之前的std::enable_if_t的过程,因为“ _t”正好是typename(请参见std::enable_if_t的定义)

非主题:我不是语言层,据我所知

 std::enable_if_t<std::is_same_v<U,T>>* = nullptr

不是完全合法;我建议使用int而不是void *

重写
 std::enable_if_t<std::is_same_v<U,T>, int> = 0

或者bool

 std::enable_if_t<std::is_same_v<U,T>, bool> = true

或其他整数类型

最后,我建议如下重写您的at()方法

template <typename U = T, 
          std::enable_if_t<std::is_same_v<U, T>, int> = 0,
          std::enable_if_t<!std::is_pointer_v<U>, int> = 0>
inline T& at(i32 i) 
{
    return e[i];
}

template<typename U = T,
         std::enable_if_t<std::is_same_v<U, T>, int> = 0,
         std::enable_if_t<std::is_pointer_v<U>, int> = 0>
    inline typename std::remove_pointer_t<T>& at(i32 i)
{
    return *e[i];
}

答案 1 :(得分:1)

有几个原因,您需要提供完整的编译错误。关于您的代码,您似乎使用C ++ 17。

例如,在这段代码中,您试图返回对存储在数组中的对象的引用,对吗?:

inline typename std::remove_pointer<T>::type& at(i32 i) {
        return *e[i];
}

可以用更像STL的代码替换:

using reference = T&;
using const_reference = const T&;

reference at(i32 i) {
    return e[i];
}

const_reference at(i32 i) const {
    return e[i];
}

或使用auto

auto at(i32 i) const {
    return e[i];
}

这是大多数STL容器的工作方式。例如,如果您访问std::vector<T*>,它将返回对T *的引用,而不是对T指向的数据的引用。

关于您使用的SFINAE技术,我不确定它是否编写正确。

例如,查看此post,以查找有关编写用于选择构造函数的条件的正确方法的信息。小总结:

template <typename = typename std::enable_if<... condition...>::type>
explicit MyAwesomeClass(MyAwesomeClass<otherN> const &);

例如,如果您只想为那些不包含指针类型的实例启用构造函数:

template<typename = typename std::enable_if_t<!std::is_pointer_v<T>>>
explicit rtvec(const rtvec& in) : d(in.d), e(new T[in.d]) {
    for (i32 i = 0; i < d; ++i)
        at(i) = in.at(i);
}

现在,关于您正在使用C ++ 17的事实,您可以使用constexpr if,这将使您的生活更加轻松,并处理各种情况。我猜是这样的:

template <typename U>
explicit rtvec(const rtvec<U>& in) : d(in.d), e(new T[in.d]) {    
     for (i32 i = 0; i < d; ++i){
         if constexpr (std::is_pointer<T>::value &&
                       std::is_pointer<U>::value) {
             // ...
         } else if constexpr (!std::is_pointer<T>::value &&
                       std::is_pointer<U>::value) {
             // ...
         } else {
             //  rest of possible combinations
         }
     }
}