3

I have a RAII wrapper around a Foo* where Foo is a library.

The class looks pretty much like this:

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);
  }
};

Error handling and assignment operators omitted for brevity.

The problem now is, that foo_clone is available in 1 library version, but not an earlier one and the wrapper should support both. There is no *_VERSION macro for me to check so I need to do it in C++ instead of the preprocessor.

I though of something like:

template<class T, decltype(foo_clone(std::declval<T>().p)) = nullptr>
Handle(const T& f){...}

But this doesnt work: The defined move-ctor requires me to add a copy ctor verbatim as Handle(const Handle&), no template trickery seems to be allowed as otherwise the compiler considers the copy-ctor as implicitely deleted.

What can I do?

Flamefire
  • 5,313
  • 3
  • 35
  • 70
  • Check https://stackoverflow.com/questions/8814705/how-to-check-if-the-function-exists-in-c-c – Paulo1205 Jul 29 '19 at 18:33
  • @Paulo1205: OP's main issue is to SFINAE **special members** which cannot be templated. even if OP's detection code is incorrect. – Jarod42 Jul 30 '19 at 10:08
  • You sure the code is incorrect? `foo_clone` (if exists) returns a pointer so this method is similar to `template* = nullptr>` For a boolean solution another method must be used of course. – Flamefire Jul 30 '19 at 11:48
  • @Flamefire: there is at least: `std::declval::p` -> `std::declval().p`. Might be considered as typo, or as your question. – Jarod42 Aug 01 '19 at 09:28
  • Thanks, fixed. Indeed a typo while modifying the code for SO – Flamefire Aug 01 '19 at 10:58

1 Answers1

3

You cannot indeed SFINAE that special member which should not be template.

So you can template the class itself:

// 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>;

In C++20, you might do a little better thanks to requires to "discard" methods:

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>;
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • I guessed something like this but wanted to avoid having to implement most of the stuff twice. There are also the assignments, `p` is of course private, so there is a getter... I guess even C++20 concepts can't solve that? (I need C++14 anyway, but curious) – Flamefire Jul 30 '19 at 07:45
  • 1
    C++20 allows some simplifications. And prior to C++20, you might factorize by using base class `HandleImpl` (with specialization) to handle `protected` `Foo* p` (constructor/destructor/copy/assignment), and derived class for extra method. – Jarod42 Jul 30 '19 at 10:02