如果找到函数,则启用拷贝构造函数。

3

我有一个RAII包装器,包装了一个指向库中Foo的指针Foo*

该类大致如下:

struct Handle{
  Foo* p;
  Handle(): p(foo_new()){}
  Handle(Handle&& f): p(std::exchange(f.p, nullptr)){}
  Handle(const Handle& f){
    p = foo_clone(f.p);
  }
};

为了简洁起见,错误处理和赋值运算符已被省略。
现在的问题是,foo_clone 在一个库版本中可用,但在早期版本中不可用,而封装应该支持两种情况。我没有*_VERSION宏可以检查,因此需要在C++中完成,而不是在预处理器中。
我想到了以下方法:
template<class T, decltype(foo_clone(std::declval<T>().p)) = nullptr>
Handle(const T& f){...}

但这样是不行的:定义的移动构造函数要求我原封不动地添加一个复制构造函数,即Handle(const Handle&),否则编译器会认为复制构造函数被隐式删除,无法使用任何模板技巧。

我该怎么办?


检查函数是否存在于 C/C++ 中,请参考以下链接 https://dev59.com/qWoy5IYBdhLWcg3wCpoz - Paulo1205
@Paulo1205:OP的主要问题是SFINAE无法模板化特殊成员,即使OP的检测代码不正确。 - Jarod42
你确定代码有误吗?如果存在foo_clone,它会返回一个指针,因此该方法类似于template<T, std::enable_if_t<...>* = nullptr>。当然,如果要使用布尔解决方案,则必须使用另一种方法。 - Flamefire
@Flamefire:至少有一个问题:std::declval<T>::p应该是std::declval<T>().p。这可能被认为是笔误,或者与您的问题相关。 - Jarod42
谢谢,已修复。在修改SO代码时确实有一个打字错误。 - Flamefire
1个回答

3

您无法使用SFINAE技术来选择那些不应该是模板的特殊成员。

因此,您可以为类本身提供模板:

// I let you implement traits has_foo_clone<T>


template <typename T, bool = has_foo_clone<T>::value>
struct HandleImpl
{
  T* p;
  HandleImpl(): p(foo_new()) {}
  HandleImpl(HandleImpl&& f): p(std::exchange(f.p, nullptr)){}
  HandleImpl(const HandleImpl& f){ p = foo_clone(f.p); }
};

template <typename T>
struct HandleImpl<T, false>
{
    T* p;
    HandleImpl(): p(foo_new()) {}
    HandleImpl(HandleImpl&& f): p(std::exchange(f.p, nullptr)){}
    HandleImpl(const HandleImpl& f) = delete;
};

using Handle = HandleImpl<Foo>;

在 C++20 中,由于 requires 可以“丢弃”方法,您的表现可能会更好。
template <typename T>
struct HandleImpl
{
  T* p;
  HandleImpl(): p(foo_new()) {}
  HandleImpl(HandleImpl&& f): p(std::exchange(f.p, nullptr)){}
  HandleImpl(const HandleImpl& f) requires(has_foo_clone<T>::value) { p = foo_clone(f.p); }
};

using Handle = HandleImpl<Foo>;

我猜到了这样的情况,但是希望避免大部分的工作都要实现两次。还有一些赋值操作,p当然是私有的,所以需要一个getter... 我想即使是C++20的概念也无法解决这个问题吧?(我需要C++14,但很好奇) - Flamefire
1
C++20 允许一些简化。在 C++20 之前,您可以使用基类 HandleImpl(带有特化)来处理 protected Foo* p(构造函数/析构函数/复制/赋值),并使用派生类进行额外的方法分解。 - Jarod42

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接