【问题标题】:Add operator[] for vector when Type is unique_ptr当 Type 为 unique_ptr 时为向量添加 operator[]
【发布时间】:2016-09-30 06:04:14
【问题描述】:

假设我们有下面的向量类,它已经被缩短到最小来展示问题。

template <typename T>
class VectorT : private std::vector<T>
{
  using vec = std::vector<T>;
public:
  using vec::operator[];
  using vec::push_back;
  using vec::at;
  using vec::emplace_back;

  // not sure if this is the beast way to check if my T is really a unique_ptr
  template<typename Q = T>
  typename Q::element_type* operator[](const size_t _Pos) const { return at(_Pos).get(); }
};

有什么方法可以检查 T 是否为 unique_ptr,如果是则添加 operator[] 以返回 unique_ptr::element_type*。同时,虽然普通的 operator[] 也应该可以工作。

VectorT<std::unique_ptr<int>> uptr_v; 
uptr_v.emplace_back(make_unique<int>(1));
//int* p1 = uptr_v[0]; // works fine if using vec::operator[]; is commented out
                       // then of course it wont work for the normal case
//std::cout << *p1;

VectorT<int*> v;
v.emplace_back(uptr_v[0].get());
int *p2 = v[0];
std::cout << *p2;

有什么方法可以实现这样的目标吗?

已编辑

我要求这个的原因是因为我可以说我的容器

class MyVec: public VectorT<std::unique_ptr<SomeClass>>

但我也可以有一个

class MyVecView: public VectorT<SomeClass*>

这两个类的功能几乎相同。所以我试图通过做类似的事情来避免重复

template<typename T>
void doSomething(VectorT<T>& vec)
{
    SomeClass* tmp = nullptr;

    for (size_t i = 0; i < vec.size(); ++i)
    {
        tmp = vec[i]; // this has to work though
        ....
    }
}

那我当然可以

MyVec::doSomething(){doSomething(*this);}
MyVecView::doSomething(){doSomething(*this);}

这当然意味着operator[] 必须适用于这两种情况

【问题讨论】:

  • int *p1 = uptr_v[0].get(); 也许?
  • @MatsPetersson 编辑了我的答案,解释了我的最终目标以及为什么这不是真正的解决方案
  • @slawekwin 我只想让 operator[] 过载。 SFINAE 部分工作正常,但using operator[] 和我写的那个部分表现不佳。我缺少一些东西,我认为专业化在这种情况下不会有帮助。
  • 请注意:从标准容器派生时要非常小心。见variousSO postsabout it

标签: c++ templates c++11 sfinae


【解决方案1】:

这里的目标是只有一个operator[]。具有多个 operator[] 的技术违反了 DRY(不要重复自己),并且很难避免使用其主体在实例化时无法编译的模板方法(根据严格阅读标准,这可能会导致您的代码格式错误)。

所以我要做的就是像这样模拟“把东西变成指针”:

namespace details {
  template<class T>
  struct plain_ptr_t;

  //specialzation for T*
  template<class T>
  struct plain_ptr_t<T*> {
    T* operator()(T* t)const{return t;}
  };

  //specialzation for std::unique_ptr
  template<class T, class D>
  struct plain_ptr_t<std::unique_ptr<T,D>> {
    T* operator()(std::unique_ptr<T>const& t)const{return t.get();}
  };

  //specialzation for std::shared_ptr
  template<class T>
  struct plain_ptr_t<std::shared_ptr<T>> {
    T* operator()(std::shared_ptr<T>const& t)const{return t.get();}
  };
}

struct plain_ptr {
  template<class T>
  typename std::result_of< details::plain_ptr_t<T>( T const& ) >::type
  operator()( T const& t ) const {
    return details::plain_ptr_t<T>{}( t );
  }
};

现在plain_ptr 是一个函子,它将智能指针映射到普通指针,并将指针映射到指针。

它拒绝不是指针的东西。如果您愿意,您可以将其更改为让它们通过,但这需要一点小心。

然后我们使用它们来改进您的operator[]

typename std::result_of< plain_ptr(typename vec::value_type const&)>::type
operator[](size_t pos) const {
  return plain_ptr{}(at(pos));
}

请注意,它不再是 template

live example.

【讨论】:

  • 有趣的方法,我也不喜欢违反 DRY,尽管我不认为 SFINAE 方法违反它。但是我无法完成这项工作,我不确定出了什么问题。我在实时示例中添加了一个类型名 ideone.com/EQq4wd 如果可能的话,我将不胜感激。
  • @xerion 大多数情况下,plain_ptr::operator() 中缺少一个 ::type
【解决方案2】:
template<typename T> struct unique_ptr_type { };
template<typename T> struct unique_ptr_type<std::unique_ptr<T>> { using type = T; };

namespace detail {
    template<typename T> std::false_type is_unique_ptr(T const&);
    template<typename T> std::true_type is_unique_ptr(std::unique_ptr<T> const&);
}
template<typename T>
using is_unique_ptr = decltype(detail::is_unique_ptr(std::declval<T>()));

template<typename T>
class VectorT : std::vector<T> {
    using vec = std::vector<T>;

public:
    using vec::at;
    using vec::emplace_back;
    using vec::push_back;

    template<typename Q = T,
             typename std::enable_if<!is_unique_ptr<Q>::value>::type* = nullptr>
    Q& operator [](std::size_t pos) { return vec::operator[](pos); }
    template<typename Q = T,
             typename std::enable_if<!is_unique_ptr<Q>::value>::type* = nullptr>
    Q const& operator [](std::size_t pos) const { return vec::operator[](pos); }

    template<typename Q = T,
             typename U = typename unique_ptr_type<Q>::type>
    U* operator [](std::size_t pos) { return vec::operator[](pos).get(); }
    template<typename Q = T,
             typename U = typename unique_ptr_type<Q>::type>
    U const* operator [](std::size_t pos) const { return vec::operator[](pos).get(); }
};

Online Demo

SFINAE 用于仅在 Tstd::unique_ptr&lt;T&gt; 时启用自定义 operator[],否则仅启用 std::vector&lt;T&gt;::operator[]

【讨论】:

  • 我选择这个作为正确答案,因为它更接近我的要求。尽管如此,还有更多正确的答案
【解决方案3】:

如果你坚持使用简单的指针,你可以写类似的东西

template<class T> auto plain_ptr(T* p) { return p; }
template<class T> auto plain_ptr(std::unique_ptr<T>& p) { return p.get(); }

然后做

tmp = plain_ptr(vec[i]); // tmp will be SomeClass* 

或者你可以有本地的 tmp:

template<typename T>
void doSomething(VectorT<T>& vec)
{

    static_assert(std::is_same<T, SomeClass*>::value || 
      std::is_same<T, std::unique_ptr<SomeClass>>::value, 
      "doSomething expects vectors containing SomeClass!");

    for (std::size_t i = 0; i < vec.size(); ++i)
    {
        auto tmp = vec[i]; 
        // ... (*tmp).foo or tmp->foo ...
    }
    // or perhaps even
    for(auto&& tmp : vec)
    {
        // ... again (*tmp).foo or tmp->foo ...
    }

}

【讨论】:

  • 有时简单更好。我可以很容易地保持我的 vectorT 类不变,并毫不费力地使用它。谢谢
猜你喜欢
  • 2017-10-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-23
  • 2018-07-11
  • 1970-01-01
  • 2016-08-12
  • 2013-02-18
相关资源
最近更新 更多