нетиповой параметр зависит от аргументов, которые идут после него

Я пишу шаблонный класс, который инкапсулирует RAII-обработчики win32. Это то, что у меня есть до сих пор:

#define NOMINMAX
#include <Windows.h>
#include <functional>

// problem: optionally pass additional params to the deleter function
template<typename ResourceT, std::function<void(ResourceT)> &Deleter>
class Win32Raii 
{
   Win32Raii(const Win32Raii &);
   Win32Raii &operator=(const Win32Raii &);

public:
   Win32Raii()
      : m_resource(nullptr)
   {}

   Win32Raii(const ResourceT &r)
      : m_resource(r)
   {}

   Win32Raii(Win32Raii &&other)
      : m_resource(nullptr)
   {
      *this = std::move(other);
   }

   ~Win32Raii()
   {
      if (m_resource)
      {
         Deleter(m_resource);
      }
   }

   Win32Raii &operator=(Win32Raii &&other)
   {
      std::swap(m_resource, other.m_resource);

      return *this;
   }

   ResourceT get() const { return m_resource; }

private:
   ResourceT m_resource;
};

// library code for each resource type
std::function<void(HICON)>destroy_icon = [](HICON h){ ::DestroyIcon(h); };
std::function<void(HDC) > delete_dc    = [](HDC h){ ::DeleteDC(h); };

//problem: pass real HWND first arg, not just a nullptr constant
std::function<void(HDC) > release_dc = [](HDC dc) { ::ReleaseDC(nullptr, dc); };

typedef Win32Raii<HICON, destroy_icon> HiconRaii;
typedef Win32Raii<HDC,   delete_dc   > HdcDelRaii;
typedef Win32Raii<HDC,   release_dc  > HdcRelRaii;
typedef Win32Raii<HMENU, destroy_menu> HmenuRaii;

//client usage examples
void main()
{
  HWND hWnd = ::FindWindowA(nullptr, "some window");
  // problem: pass hWnd
  HdcRelRaii rdc(::GetDC(hWnd) /*, hWnd */);
  HdcDelRaii ddc(::CreateCompatibleDC(rdc.get()));

  HiconRaii h;
  HiconRaii h2(::LoadIconW(nullptr, IDI_APPLICATION));

  h = HiconRaii(std::move(h2));

  HiconRaii h3 = std::move(h);

  h3 = HiconRaii();
}

Это хорошо для API, которые принимают один аргумент HANDLE и освобождают его. Теперь моя проблема заключается в API, которые принимают несколько аргументов для выпуска дескриптора, например

ReleaseDC(HWND, HDC);
SelectObject(HDC, HGDIOBJ);

Синтаксис использования клиента, которого я хочу достичь в случае нескольких параметров функции удаления:

HdcRelRaii dc_to_release_in_dtor(::GetDC(hWnd), hWnd);
SelectObjRaii obj_to_reselect_in_dtor(::SelectObject(hBrush, hDC), hDC);

Итак, наконец, вопрос: как я могу изменить

template<typename ResourceT, std::function<void(ResourceT)> &Deleter>

к чему-то вариативному, например

template<
  typename ResourceT,
  std::function<void(ResourceT, Args&...) &Deleter,
  typename... Args>
>

?

Очевидно, что параметр Deleter, не являющийся типом, зависит от аргументов, которые идут после него, что недопустимо. Отсюда: застрял...

заранее спасибо

P.S. приветствуются любые идеи по лучшему заголовку, лучшему синтаксису использования и т.д.


person Yury Korobkin    schedule 16.03.2014    source источник
comment
В любом случае вам не разрешено использовать объекты типа класса в качестве параметров шаблона.   -  person Brian Bi    schedule 17.03.2014
comment
скопировать, что делает shared_ptr?   -  person Yakk - Adam Nevraumont    schedule 17.03.2014
comment
Вы должны сделать Deleter параметром шаблона типа и создать объект-член типа Deleter в своем конструкторе, а затем вызвать его. Вот как, например, std::set сравнивает объекты   -  person Brian Bi    schedule 17.03.2014
comment
@Brian: вам разрешено, если это ссылка или указатель на статический объект   -  person Yury Korobkin    schedule 17.03.2014
comment
Да, но указатели и ссылки не являются объектами типа класса;)   -  person Brian Bi    schedule 17.03.2014
comment
@Brian Еще лучше иметь ссылку на элемент Deleter и инициализировать его в конструкторе.   -  person stardust    schedule 17.03.2014
comment
Почему вы не можете сказать template<typename Signature, std::function<Signature> &Deleter> и адаптировать синтаксис клиента к Win32Raii<void(HICON, HDC), destroy_icon_with_hdc> ?   -  person Johannes Schaub - litb    schedule 17.03.2014
comment
@Yakk shared_ptr или unique_ptr требуют, чтобы средство удаления передавалось c'tor при каждом вызове c'tor. Я хотел бы более чистое использование клиента. Кроме того: им также нужен модуль удаления, который принимает один аргумент. Любые дополнительные аргументы, которые мне нужно будет связать и передать в каждом таком создании smart_ptr.   -  person Yury Korobkin    schedule 17.03.2014
comment
@JohannesSchaub-litb Как тогда будет выглядеть вызов удаления в d'tor? Сейчас это Deleter(m_resource); Как я могу адаптировать его к необязательным дополнительным параметрам?   -  person Yury Korobkin    schedule 17.03.2014
comment
@YuryKorobkin использует кортеж для хранения аргументов и распаковывает их, как описано в stackoverflow.com/a/7858971/34509.   -  person Johannes Schaub - litb    schedule 17.03.2014
comment
В большинстве случаев использования shared_ptr не требуется передавать конструкторам удаляющие элементы: только если они имеют зависимости для каждого экземпляра. Если по умолчанию один из типов выполняет задание, например.   -  person Yakk - Adam Nevraumont    schedule 17.03.2014
comment
Я согласен с @Yakk. Вы все еще можете использовать DeleterType<destroy_icon> в качестве типа удаления, чей operator() просто будет делать то, что вы сейчас делаете напрямую.   -  person Johannes Schaub - litb    schedule 17.03.2014


Ответы (1)


Вот как это могло бы работать (используя make_index_sequence из C++1y). Не уверен, что вы планируете использовать этот ссылочный параметр, хотя

template<typename Signature, std::function<Signature> &Deleter>
class Win32Raii;

template<typename ...Types, std::function<void(Types...)> &Deleter>
class Win32Raii<void(Types...), Deleter> { 
public:
   ~Win32Raii() {
      // do as proposed in http://stackoverflow.com/a/7858971/34509
      callDelete(std::make_index_sequence<sizeof...(Types)>{});
   }

private:
   template<std::size_t ...I>
   void callDelete(std::integer_sequence<std::size_t, I...>) {
       Deleter(std::get<I>(args)...);
   }

   std::tuple<Types...> args;
};

вот так бы я поменял

template<typename Deleter, typename ...Params>
class Win32Raii {
public:
   Win32Raii(Params... args, Deleter deleter = Deleter());
   ~Win32Raii() { /* copy most from above ... */ }

private:
   Deleter deleter;
   std::tuple<Params...> args;
};

Это полностью совместимо со слоем косвенности

template<typename Type, Type &Deleter>
struct ExoticDeleter {
   template<typename ...T>
   void operator()(T&&...t) const (
      Deleter(std::forward<T>(t)...);
   }
};

typedef Win32Raii< 
   ExoticDeleter<decltype(destroy_icon), destroy_icon>,
   HICON
> HiconRaii;

Я изменил порядок, чтобы несколько параметров можно было задавать просто как последовательность без их переноса. Но это всего лишь дело вкуса.

person Johannes Schaub - litb    schedule 16.03.2014