使用enable_if澄清成员函数模板特化

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

我想了解我在试图最小化我的成员函数模板专业化的详细程度时出错了。我这样做时会出现编译错误。这是有效的版本,希望能够揭示我想要实现的目标:

#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];
    }*/


};

(只要与指针有关的部分也在主要注释掉)。我想了解是什么让第二个代码无法编译。

我没有直接专门化这个类的原因是在我原来的实现中我有很多其他成员函数,它们在两个我不想重复的特化之间是等效的。

c++ templates sfinae member-functions enable-if
2个回答
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)时,SFINAE不起作用,因为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
投票

有几个原因,您需要提供完整的编译错误。关于你的代码,你似乎使用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,找到有关编写选择构造函数条件的正确方法的信息。小sumary:

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
         }
     }
}   
© www.soinside.com 2019 - 2024. All rights reserved.