2018/09/14

c++ Signals and Slots 구현


이전 글의 Delegate 개념을 이용해서 Signals and Slots를 구현해 보았다. 구글링해 보면 Qt의 Signals and Slots를 구현해 보려고 했던 과거의 여러 시도들을 찾을 수 있다. Libsigc++이 대표적인 사례이다. 물론, Boost signals2/signals도 있다. 잘 굴러가는 바퀴들이 많은 데 잘 굴러가지 않을 가능성이 높은 바퀴를 또 만들고 싶은 것은 단순한 호기심 때문이다. 그 호기심의 근원은 저렇게 복잡하게 별의 별짓을 다해 가면서 구현한 방법들이 맘에 들지 않았고, 더 쉬운 방법이 있지 않을까 하는 것이다.

c++에서 가능한 모든 종류의 함수 객체(function object)들을 저장했다가 원하는 시점에 한꺼번에 호출할 수 있는 방법이 Signals and Slots이다. 근본적으로는 class 간의 coupling 문제를 해결해 주기 때문에 program 설계와 재사용 측면에서 매우 중요한 개념이기도 하다. 여기서, 함수 객체는 일반 static/global 함수와 class member 함수 뿐만 아니라, lambda(capture 포함), std::function이나 사용자가 만든 functor 등의 모든 호출 가능한 객체들을 의미한다.

앞서 Delegate에 대한 글에서 얘기했듯이 표준 c++에서 member function에 대한 pointer 크기가 가변적이므로 Delegate나 Signal & Slots를 표준 c++에서 구현하기가 매우 어렵다. c++ 언어의 장점이 막강한 유연성을 기반으로 구현 못할 것이 없는 언어인데 member function pointer에 대한 제약이 있다는 것은 굉장히 놀라운 사실이다. 물론, modern c++가 추구하는 바는 가능한 raw pointer를 사용하지 못하게 하는 것이긴 하지만, 언어 자체에 제약사항이 있는 것은 바람직하지 않다고 본다. 더구나 pointer가 없는 c/c++는 앙꼬빠진 찐빵이다. 추측컨대 MS를 비롯한 상용 컴파일러 Vendor들이 자신들의 컴파일러가 갖고 있는 제약사항이기 때문에 표준으로 채택하지 않았을 가능성이 매무 높다고 본다. 왜냐하면 gcc/g++나 clang에서는 표준이 아님에도 이미 고정 크기 member function pointer를 사용하기 때문이다. 이것이 중요한 이유는 모든 함수 객체를 쉽게 저장했다가 나중에 호출할 수 있기 때문이다.

이 글은 표준 c++ 방식은 아니지만 member function pointer를 저장하는 것이 Signals and Slots를 구현하는데 얼마나 편리한지를 보여주는 구체적인 예가 될 것이다. 컴파일 오류가 나면 컴파일러가 지원하지 않는다는 것을 알 수 있다.

모든 종류의 함수 객체를 저장하는 데는, 객체 자신에 대한 pointer와 class member function의 경우 이에 대한 pointer를 포함해서, 두 개의 저장소면 충분하다. 가령, static/global 함수는 객체 pointer에 저장할 수 있다. std::function이나 functor, lambda는 모두 class object와 동일하다.

c++은 strictly typed language이기 때문에 저장했던 pointer들을 원래의 type으로 변환해야 함수 객체들을 실행할 수 있는데, 같은 type에 대한 pointer의 크기가 동일하다면 generic class 객체의 pointer를 이용해서 원래의 함수 객체들을 호출할 수 있다. 이것이 reinterpret_cast를 사용하는 이유이고 또한 이를 사용할 경우 컴파일러에 대한 portability가 낮아질 수 밖에 없는 이유이기도 하다.

Signal 객체 하나는 동일한 arguments와 return type을 갖는 서로 다른 종류의 Slot 함수 객체들을 std::vector container에 저장한다. 얼핏 생각하면 서로 다른 종류의 함수 객체들을 std::vector에 저장하려면 c++17의 std::any 객체를 사용해야 하는 것이 아닌가 할 수도 있지만, template type으로는 모두 동일한 type이 되므로 std::any객체를 사용할 필요는 없다. std::set이나 std::unordered_set이 container로써 적합해 보이지만, Signal에 connect되는 순서에 따라 Slot 객체 들의 실행 순서가 결정되기 때문에 vector를 사용하되, 이미 container에 있는 객체는 다시 connect해도 중복으로 container에 저장되지 않도록 했다.

아래 소스에는 dangling pointer에 대해 고려하지 않았다. Signals & Slots도 Observer Pattern의 일종이라 생각할 수도 있는데, Signal에 연결된 Slot 객체가 Signal 보다 먼저 소멸했을때 Signal에 저장된 Slot pointer 들이 좀비가 되어 문제가 생길 수 있다. multi-thread를 사용하지 않는 한 크게 문제되지는 않겠지만, 여기서는 smart pointer를 사용하지 않는 방식이기 때문에 나중에 고려해 볼 예정이다.

아무튼 여기서 소개한 Signals & Slots의 장점은, code가 직관적이고, Slot 객체에 대해 복사 또는 복제하는 방식이 아니고 메모리를 직접 참조하므로 성능이 좋을 가능성이 높다. 단점은 c++ 표준 방식이 아니므로 compiler 호환성 또는 이식성이 떨어질 수 밖에 없다.


// Signals & Slots C++ Implementation
//
// This program is copyright (c) 2018 by Umundu @ https://zapary.blogspot.com .
// It is distributed under the terms of the GNU LGPL version 3, as detailed in
// https://opensource.org/licenses/lgpl-3.0.html .

#include <vector>

class TGenC;

class Store
{
public:
  bool operator!() const { return !m_obj && !m_pmf; }
  bool operator==(const Store& rhs) const
  { return m_obj == rhs.m_obj && m_pmf == rhs.m_pmf; }

protected:
  using TGvPMF = void(TGenC::*)();

  TGenC* m_obj {nullptr};       // pointer to an object or a function
  TGvPMF m_pmf {nullptr};       // pointer to a member function
};

template <typename TR, typename... TArgs>
class Slot : public Store
{
private:
  using TypePF = TR(*)(TArgs...);
  using TypePMF = TR(TGenC::*)(TArgs...);
  template<typename TF>
  using TypeIfRValue = typename std::enable_if<!std::is_reference<TF>{}>::type;

public:
  Slot(TypePF pf) { bind(pf); }

  template <typename TB, typename TPMF>
  Slot(const TB* pobj, TPMF pmf) { bind(pobj, pmf); }

  template<typename TB, typename TPMF, typename = TypeIfRValue<TB>>
  Slot(TB&& pobj, TPMF pmf) { bind(std::forward<TB>(pobj), pmf); }

  TGenC* object() const { return m_obj; }
  TypePMF pmf() const { return reinterpret_cast<TypePMF>(m_pmf); }

  TR operator()(TArgs... args) const { emit(args...); }

  TR emit(TArgs... args) const
  {
    if(!m_obj) return;
    if(m_pmf) return (m_obj->*(pmf()))(args...);
    else return (*reinterpret_cast<TypePF>(m_obj))(args...);
  }

  void bind(TypePF pf)
  {
    static_assert(sizeof(TGenC*) == sizeof(pf), "Compiler unsupported.");
    m_obj = reinterpret_cast<TGenC*>(pf);
  }

  template <typename TB, typename TPMF>
  void bind(const TB* pobj, TPMF pmf)
  {
    static_assert(sizeof(TGvPMF) == sizeof(pmf), "Compiler Unsupported.");
    m_pmf = reinterpret_cast<TGvPMF>(pmf);
    m_obj = reinterpret_cast<TGenC*>(const_cast<TB*>(pobj));
  }

  template<typename TB, typename TPMF>
  TypeIfRValue<TB> bind(TB&& pobj, TPMF pmf)
  {
    static_assert(sizeof(TGvPMF) == sizeof(pmf), "Compiler Unsupported.");
    m_pmf = reinterpret_cast<TGvPMF>(pmf);
    m_obj = reinterpret_cast<TGenC*>(&pobj);
  }
};

template <typename T> class Signal;
template <typename TR, typename... TArgs>
class Signal<TR(TArgs...)>
{
private:
  using TypePF = TR(*)(TArgs...);
  using TypeSlot = Slot<TR, TArgs...>;
  template<typename TF>
  using TypeIfRValue = typename std::enable_if<!std::is_reference<TF>{}>::type;

public:
  Signal() = default;

  Signal(TypePF pf) { if(!*this) connect(pf); }

  template <typename TF>
  Signal(TF* pobj) { if(!*this) connect(pobj); }

  template <typename TB, typename TO>
  Signal(TO* pobj, TR(TB::*pmf)(TArgs...))
  { if(!*this) connect(pobj, pmf); }

  template <typename TB, typename TO>
  Signal(const TO* pobj, TR(TB::*pmf)(TArgs...) const)
  { if(!*this) connect(pobj, pmf); }

  ~Signal() { disconnect(); }

  template <typename TPF>
  Signal& operator=(TPF pf) { if(!*this) connect(pf); }

  template <typename TF>
  Signal& operator=(TF* pobj) { if(!*this) connect(pobj); }

  const TypeSlot& operator[](int i) const { return m_slots.at(i); }
  bool operator!() const { return m_slots.empty(); }
  bool operator==(const Signal& rhs) const { return m_slots == rhs.m_slots; }
  bool operator!=(const Signal& rhs) const { return !operator==(rhs); }
  void operator()(TArgs... args) const { emit(args...); }

  void emit(TArgs... args) const
  {
    for(auto it = m_slots.cbegin(); it != m_slots.cend(); ++it) (*it)(args...);
  }

  int size() const { return m_slots.size(); }

  bool contains(TypeSlot& slot)
  {
    for(auto it = m_slots.cbegin(); it != m_slots.cend(); ++it)
      if(*it == slot) return true;
    return false;
  }

  void connect(TypePF pf)
  {
    if(!pf) return;
    TypeSlot tmp(pf);
    if(!contains(tmp)) m_slots.push_back(std::move(tmp));
  }

  template <typename TB, typename TO>
  void connect(TO* pobj, TR(TB::*pmf)(TArgs...))
  {
    if(!pobj) return;
    TypeSlot tmp(implicit_cast<const TB*>(pobj), pmf);
    if(!contains(tmp)) m_slots.push_back(std::move(tmp));
  }

  template <typename TB, typename TO>
  void connect(const TO* pobj, TR(TB::*pmf)(TArgs...) const)
  {
    if(!pobj) return;
    TypeSlot tmp(implicit_cast<const TB*>(pobj), pmf);
    if(!contains(tmp)) m_slots.push_back(std::move(tmp));
  }

  template <typename TF>
  void connect(TF* pobj)
  {
    if(!pobj) return;
    TypeSlot tmp(pobj, &TF::operator());
    if(!contains(tmp)) m_slots.push_back(std::move(tmp));
  }

  template<typename TF>
  TypeIfRValue<TF> connect(TF&& pobj)
  {
    TypeSlot tmp(std::forward<TF>(pobj), &TF::operator());
    if(!contains(tmp)) m_slots.push_back(std::move(tmp));
  }

  template <typename TB, typename TO>
  void disconnect(const TO* pobj, TR(TB::*pmf)(TArgs...))
  {
    if(!pobj) return;
    TypeSlot tmp(pobj, pmf);
    for(auto it = m_slots.cbegin(); it != m_slots.cend(); ++it) {
      if(*it == tmp) {
        m_slots.erase(it);
        return;
      }
    }
  }

  template <typename TB, typename TO>
  void disconnect(const TO* pobj, TR(TB::*pmf)(TArgs...) const)
  {
    if(!pobj) return;
    TypeSlot tmp(implicit_cast<const TB*>(pobj), pmf);
    for(auto it = m_slots.cbegin(); it != m_slots.cend(); ++it) {
      if(*it == tmp) {
        m_slots.erase(it);
        return;
      }
    }
  }

  template <typename TO>
  void disconnect(const TO* pobj)
  {
    if(!pobj) return;
    TGenC* tmp = reinterpret_cast<TGenC*>(const_cast<TO*>(pobj));
    for(auto it = m_slots.cbegin(); it != m_slots.cend();) {
      if(it->object() == tmp) it = m_slots.erase(it);
      else ++it;
    }
  }

  void disconnect(TypePF pf)
  {
    if(!pf) return;
    TypeSlot tmp(pf);
    for(auto it = m_slots.cbegin(); it != m_slots.cend(); ++it) {
      if(*it == tmp) {
        m_slots.erase(it);
        return;
      }
    }
  }

  void disconnect() { m_slots.clear(); }

private:
  template <typename TO, typename TI>
  TO implicit_cast(TI in) { return in; }

  std::vector<TypeSlot> m_slots;
};

2018/04/30

Ubuntu 18.04 LTS 설치


지난 주에 우분투 18.04 LTS가 배포됐다. 향후 5년간 지원될 버전이니 이것 만으로도 18.04로 갈아탈 충분한 이유가 될 수 있다. 다만, 17.10 사용자라면 17.10에서 많은 변화가 있었기에 18.04에서 달라진 걸 크게 느끼지 못할 것이다. 원래는 Upgrade하려고 Sofware Update를 실행했는데 뭐가 문제인지 부분 Upgrade를 시킬 것이란 경고가 떴다. 찜찜해서 그냥 하드디스크의 iso 설치 이미지를 이용한 Clean install을 감행했다. 그런데, iso 이미지로 부팅시 오류가 발생해서 부팅이 안된다. iso 이미지 파일 내의 리눅스 커널 명이 vmlinuz.efi에서 vmlinuz로 다시 바뀌었기 때문이다. efi를 지원하면서 vmlinuz.efi로 바뀌었었는데 이제 보편화됐다고 생각한 건지 다시 vmlinuz로 돌아왔다. 다만, Release Note에서 변경 사항이 누락된 점은 아쉽다.

우분투 설치 iso Grub boot menu entry 수정

아래의 예와 같이 Grub boot menu entry에서 linux (loop)/casper/vmlinuz.efi ... 부분을 수정해 주어야 한다. USB에서 우분투 설치 iso 파일을 사용해 설치할 경우에도 커널 명을 수정해 주어야 한다.
menuentry "HDD Ubuntu 64-bit iso" {
   set isofile="/boot-isos/ubuntu-18.04-desktop-amd64.iso"
   loopback loop (hd0,9)$isofile
   linux (loop)/casper/vmlinuz boot=casper iso-scan/filename=$isofile noprompt noeject
   initrd (loop)/casper/initrd.lz
}
하드디스크의 우분투 iso 파일을 이용하는 경우에는 grub.cfg 파일을 직접 수정하는 것이 아니므로,

$ sudo update-grub

명령을 실행한 후 재부팅해야 한다. 자세한 내용은 이전 글을 참고하는 것이 좋다.

우분투 18.04의 새로운 점들

리눅스 커널 4.15가 채택되었다. 보안 이슈 해결이나 새로운 하드웨어 지원을 위해 필요한 부분이다. 우분투 자체적으로는 커널 4.0 이후 지원되는 Kernel Live Patching 기능을 Ubuntu One을 통해 지원한다. 우분투 설치시에 사용할 지 물어 본다.

Wayland 대신 X 서버가 다시 기본 display server가 되었고 Wayland는 Option이 되었다. Wayland를 지원하는 앱들이 부족해서 LTS 버전에 적합하지 않다고 판단한 것이다. 원격 데스크탑이나 화면 및 비디오 캡춰 소프트웨어 등이 Wayland 환경에서 동작하지 않는 문제가 대표적이다. 안정성 측면에서도 아직 Wayland를 사용하기에는 이르다는 생각이다.

설치 시에 minimal install 옵션을 제공하는 것도 달라진 점이다. 설치 iso 이미지가 우분투 17.10은 1.5GB였는데 18.04는 1.9GB가 되었다. 아무튼 Web browser와 핵심 시스템 유틸리티만 설치된다고 한다.

Gnome Shell 3.28을 채택했다. 다만 File 관리자(Nautilus)는 구 버전을 customize 했는데 최신 버전에서 Desktop Icon을 지원하지 않기 때문이다. LTS 버전이라 사용자들에게 급격한 변화를 강요하지 않으려는 관점이 들어간 건데 Gnome을 채택했으면 그대로 가져다 쓰는 것이 더 낫다는 관점도 수용할 필요가 있어 보인다. Gnome 3.28은 Thunderbolt 3 기기들을 지원한다. Gnome Shell의 Ubuntu Dock extension에서 앱 아이콘을 click하면 앱이 실행되고 다시 click 하면 minimize가 안되는데 아래와 같이 설정하면 minimize 기능을 사용할 수 있다.

$ gsettings set org.gnome.shell.extensions.dash-to-dock click-action 'minimize'

그런데 위 명령은 오류가 발생했고, dconf-editor를 사용해서 위의 key 값을 변경해 주면 잘 된다.

Color 이모티콘(Emoji)을 지원한다. 우분투 17.10부터 흑백 이모티콘을 지원하기 시작했다. Characters 앱이 이전의 Character Map 앱을 대체했는데 Color 이모티콘을 보다 원활히 지원하려는 의도일 수 있다.

우분투 Software 앱에서 Snap 앱들에 대한 지원을 강화했다. Calculator, Charaters, Logs, System Monitors 앱들은 Snap 앱으로 설치된다.

$ ls -l /snap

이 밖에도 Calendar 앱에서 날씨 예보를 지원하고, To Do 앱이 추가됐다.

또한, 17.10 이후 systemd 로그를 지원하기 위해 Logs 앱이 System Log를 대체했고, Disk Usage Analyzer, Files(Nautilus), Remmina, Settings, Ubuntu Software 앱 들의 UI가 새로운 디자인으로 바뀌었다.

새로운 점들에 대한 더 자세한 내용은 우분투 18.04 Release Note를 참고하기 바란다.

한글 입력기 및 fonts

기본 입력기인 ibus를 사용하거나 fcitx-hangul을 설치해서 사용하면 된다. 우분투 17.10과 설치 방법은 동일하다. ibus 입력기의 경우 Gnome Top Panel에 한글 입력기를 표시하거나 제거하는 올바른 방법은 Settings > Region& Language > Input Sources > + 또는 - [버튼] 으로 Korean (Hangul)을 추가하거나 삭제하면 된다. 다만 fcitx-hangul을 설치할 경우 여전히 ibus 프로세스가 살아 있는 문제는 남아 있으므로 /usr/bin/ibus-daemon 파일을 rename해 줄 필요가 있다.

한글 폰트는 Noto Sans 폰트가 기본 폰트가 되면서 나눔 글꼴 패키지가 기본으로 설치되지 않더라. 아래와 같이 설치할 수 있다.

$ sudo apt install fonts-nanum fonts-nanum-coding fonts-nanum-extra

그런데 우분투 전반적으로 폰트가 커지고 bold체가 강해진 느낌이라 한글 Web Site들이 좀 불편해졌다. firefox 폰트 설정에서 "Allow pages to choose their own fonts, instead of your selections above" 옵션을 끄면 내가 설정한 폰트를 사용할 수 있다.


2018/01/04

c++ Delegate 내지는 Callback


바퀴를 발명하지 말라는 격언이 있지만 공부할 때는 바퀴를 다시 발명해 보는 것도 좋은 방법일 수 있다. Qt의 Signals & Slots를 c++에서 쉽게 구현할 수 있는 방법이 있을까 궁금해서 찾다가 15년이 다 돼가는 아주 오래된 글이지만 옷깃으로 눈물을 훔칠 만큼 감동적인 글을 보게 되었다.

FastDelegate 이란 건데 같은 codeproject 사이트에서 Delegate로 검색해 보니 FastestDelegate로 알려질 만큼 유명한 글이었다. 자세한 설명은 링크와 소스 코드를 보는게 좋다. c++11 버전으로 내가 이해할 수 있게 다시 각색해 보았다. 다만, 원래 소스는 대부분의 c++ compiler를 지원하지만 내가 각색한 소스는 g++에서만 동작할 수도 있다. 특히, 원저자가 발명한(?) horrible_cast는 해커들이나 쓰는 방식이다.  적어도 Delegate에 관한 한 원저자가 주장하듯이 표준 c++을 따르는것 보다 portable code가 더 중요하다는 관점에 동의하지 않을 수 없다. 참고로, 표준 c++을 따르는 Delegate에 관한 글과 이를 modern c++로 구현한 글도 codeproject 사이트에 올라와 있다. 표준 방식의 문제는 과도한 template 사용으로 인해 사용자 인터페이스가 많이 불편하다는 것이다.

문제의 근원은 표준 c++ class의 member function pointer가 일반 pointer와는 달리 특정한 address를 갖지 못한다는데 있다. 구글링하다 보니 누군가 c++20에 넣어 달라는 글도 보이긴 하더라. 표준 c++에 Qt의 Signals & Slots를 넣어 달라는 요청도 번번히 거절 당해 왔는데 그 이유는 표준 방식으로도 Observer Pattern이나 Delegate Pattern을 사용해서 구현할 수 있다는 것이었다. 실상은 Signals & Slots와 같이 범용적으로 쓰기에는 제약사항이 많다.

여기서 Delegate는 Callback 함수와 동일한 개념이다. 표준 방식으로 std::function을 이용해서 구현할 수도 있는데 memory allocation이 사용되는 경우 느려질 수 있고, Signals & Slots와 같이 event callback에 사용하기에는 어려운 점이 많다. 참고로 Qt는 meta object compiler(moc)를 사용해서 Signals & Slot을 구현했다. Qt framework을 사용할 수 없는 경우에 표준 c++로 Signals & Slots를 구현하기란 쉽지 않은 일인 것이다.

아무튼, FastDelegate를 이용하면 Signals & Slots를 한결 수월하게 구현할 수 있을 듯 하다. 참고로, FastDelegate은 일반 함수(static function)와 class member 함수의 Callback을 모두 지원한다. 다만, 당시에는 lambda나 variadic template이 표준 c++이 아니었는데 variadic template 지원 부분은 추가 되었고, lamda의 경우엔 capture가 없으면 사용할 수 있다. 가령, 아래의 예와 같이 사용할 수 있다.

// lambda example : no captures only
  Delegate<double(int, double)> d1([](int i, double x) -> double { return x + i; });
  Delegate<double(int, double)> d2, d3;
  auto lambda = [](int i, double x) -> double { return 2*x + i; };
  d2 = lambda;
  d3 = [](int i, double x) -> double { return x*x + i; };

  std::cout << d1(10, 3) << " " << d2(5, 6) << " " << d3(1, 1) << "\n";
// Rewrite FastDelegate.h by Don Clugston for g++ only(unportable).
// Original FastDelegate is portable to almost all the compilers.
// See http://www.codeproject.com/cpp/FastDelegate.asp for more information.

namespace HIDDEN
{

class TGeneric;
using TGenericP = TGeneric*;
using TGenericPvMF = void(TGeneric::*)();

class Memento
{
public:
  Memento() = default;
  Memento(const Memento& rhs) : m_object(rhs.m_object), m_pmf(rhs.m_pmf) {}
  Memento& operator=(const Memento& rhs)
  {
    m_object = rhs.m_object;
    m_pmf = rhs.m_pmf;
    return *this;
  }
  
  bool operator!() const { return !m_object && !m_pmf; }
  bool operator==(const Memento &rhs) const
    { return m_object == rhs.m_object && m_pmf == rhs.m_pmf; }
  bool operator<(const Memento &rhs) const
  {
    if(m_object != rhs.m_object) return m_object < rhs.m_object;
    return std::memcmp(&m_pmf, &rhs.m_pmf, sizeof(m_pmf)) < 0;
  }
  bool operator>(const Memento& rhs) const { return rhs.operator<(*this); }
  size_t hash() const
    { return reinterpret_cast<size_t>(m_object) ^ unsafe_cast<size_t>(m_pmf); }

protected:
  TGenericP m_object{nullptr};
  TGenericPvMF m_pmf{nullptr};
  
private:
  template<class TO, class TI>
  static TO unsafe_cast(TI in)
  {
    union { TO out; TI in; } u;
    u.in = in;
    return u.out;
  }
};

template<typename TGPMF, typename TPF>
class Closure : public Memento
{
public:
  TGenericP object() const  { return m_object; }
  TGPMF pmf() const  { return reinterpret_cast<TGPMF>(m_pmf); }
  TPF function() const { return horrible_cast<TPF>(this); }

  template<class TB, class TPMF>
  void bind(const TB* pobj, TPMF pmf)
  {
    static_assert(sizeof(TGenericPvMF) == sizeof(pmf), "Unsupported conversion");
    m_pmf = reinterpret_cast<TGenericPvMF>(pmf);
    m_object = reinterpret_cast<TGenericP>(const_cast<TB*>(pobj));
  }

  template<class TB, class TPMF>
  void bind(TB* pobj, TPMF pmf, TPF pf)
  {
    if(!pf) { m_object = nullptr; m_pmf = nullptr; return; }
    bind(pobj, pmf);
    m_object = horrible_cast<TGenericP>(pf);
  }

private:
  template<class TO, class TI>
  static TO horrible_cast(TI in)
  {
    union { TO out; TI in; } u;
    static_assert(sizeof(TI) == sizeof(u) && sizeof(TI) == sizeof(TO),
      "Unsupported conversion");
    u.in = in;
    return u.out;
  }
};

template<typename TR, typename... TArgs>
class DelegateImpl
{
  using TypePF = TR(*)(TArgs...);
  using TypePMF = TR(TGeneric::*)(TArgs...);

public:
  DelegateImpl() = default;
  DelegateImpl(const DelegateImpl& rhs) : m_closure(rhs.m_closure) {}
  template <typename TB, typename TO>
  DelegateImpl(TO* pobj, TR(TB::*pmf)(TArgs... args)) { bind(pobj, pmf); }
  template <typename TB, typename TO>
  DelegateImpl(const TO* pobj, TR(TB::*pmf)(TArgs... args) const){ bind(pobj, pmf); }
  template<class TPF>
  DelegateImpl(TPF pf) { bind(pf); }

  void operator=(const DelegateImpl& rhs) { m_closure = rhs.m_closure; }  
  template<class TPF>
  DelegateImpl& operator=(TPF pf) { bind(pf); }
  bool operator!() const { return !m_closure; }
  bool operator<(const DelegateImpl& rhs) const { return m_closure < rhs.m_closure; }
  bool operator>(const DelegateImpl& rhs) const { return !operator<(rhs); }
  bool operator==(const DelegateImpl& rhs) const
    { return m_closure == rhs.m_closure; }
  bool operator==(TypePF pf) const { return m_closure == pf; }
  bool operator!=(const DelegateImpl& rhs) const { return !operator==(rhs); }
  bool operator!=(TypePF pf) const { return !operator==(pf); }
  TR operator()(TArgs... args) const
    { return (m_closure.object()->*m_closure.pmf())(args...); }

  template <typename TB, typename TO>
  void bind(TO *pobj, TR(TB::*pmf)(TArgs... args))
    { m_closure.bind(implicit_cast<TB*>(pobj), pmf); }
  template <typename TB, typename TO>
  void bind(const TO* pobj, TR(TB::*pmf)(TArgs... args) const)
    {  m_closure.bind(implicit_cast<const TB*>(pobj), pmf); }
  template<class TPF>
  void bind(TPF pf) { m_closure.bind(this, &DelegateImpl::function, pf); }

private:
  template <class TO, class TI>
  static TO implicit_cast(TI in) { return in; }
  TR function(TArgs... args) const { return (*m_closure.function())(args...); }

  Closure<TypePMF, TypePF> m_closure;
};

} // namespace HIDDEN

template<typename T> class Delegate;
template<typename TR, typename... TArgs>
class Delegate<TR(TArgs...)> : public HIDDEN::DelegateImpl<TR, TArgs...>
{
  using TypeBase = HIDDEN::DelegateImpl<TR, TArgs...>;

public:
  using TypeBase::TypeBase;

  Delegate() = default;
  template <typename TB, typename TO>
  Delegate(TO* pobj, TR(TB::*pmf)(TArgs... args)) : TypeBase(pobj, pmf) {}
  template <typename TB, typename TO>
  Delegate(const TO* pobj, TR(TB::*pmf)(TArgs... args) const) : TypeBase(pobj, pmf) {}
  Delegate(TR(*pf)(TArgs... args)) : TypeBase(pf) {}
};

template <typename TR, typename... TArgs>
Delegate<TR(TArgs...)> makeDelegate(TR(*pf)(TArgs...))
  { return Delegate<TR(TArgs...)>(pf); }
template <typename TR, typename TB, typename TO, typename... TArgs>
Delegate<TR(TArgs...)> makeDelegate(TO* pobj, TR(TB::*pmf)(TArgs...))
  { return Delegate<TR(TArgs...)>(pobj, pmf); }
template <typename TR, typename TB, typename TO, typename... TArgs>
Delegate<TR(TArgs...)> makeDelegate(TO* pobj, TR(TB::*pmf)(TArgs...) const)
  { return Delegate<TR(TArgs...)>(pobj, pmf); }