Реальность такого делегата
От: glap  
Дата: 22.02.13 08:07
Оценка:
Один товарищ утверждает, что написал делегат который абстрагируется от сигнатуры функции и способен переварить нормально такой код:


struct SomeStruct
{
    operator int() { return 0; }
}

void Caller(Connector con)
{
    con( SomeStruct() );
}

void Foo1(int i) {}
void Foo2(SomeStruct ss) {}

Connector con1 = &Foo1;
Caller(con1);

Connector con2 = &Foo2;
Caller(con2);


Тоесть тут не будет у него ни UB ни исключения, а обе Foo получат что хотели. Я тут вижу одну нерешаемую проблему с безвозвратной утерей сигнатуры функции. Соответственно вызов оператора компилятору просто не сгенерироть.

Я чего-то не знаю?
Re: Реальность такого делегата
От: Кодт Россия  
Дата: 22.02.13 09:43
Оценка:
Здравствуйте, glap, Вы писали:

G>Один товарищ утверждает, что написал делегат который абстрагируется от сигнатуры функции и способен переварить нормально такой код:


Ну, это дело нехитрое. Ниже покажу, как.

G>Тоесть тут не будет у него ни UB ни исключения, а обе Foo получат что хотели.


А что хотели обе Foo? Могу предположить, что они хотели значение по умолчанию.

G> Я тут вижу одну нерешаемую проблему с безвозвратной утерей сигнатуры функции. Соответственно вызов оператора компилятору просто не сгенерироть.


class Connector
{
  boost::function< void(boost::any) > fun;

  template<class Arg>
  static void fun_impl(void(*f)(Arg), boost::any a)
  {
    if(Arg* v = boost::any_cast<Arg>(&a))
      f(*v);
    else
      f(Arg()); // или любое другое предпочитаемое поведение - не вызывать, кинуть исключение, etc...
  }

public:
  template<class Arg>
  Connector( void(*f)(Arg) ) : fun( boost::bind(fun_impl<Arg>, f, _1) ) {}

  void operator()(boost::any a) const { fun(a); }
};
Перекуём баги на фичи!
Re: Реальность такого делегата
От: enji  
Дата: 22.02.13 09:47
Оценка:
Здравствуйте, glap, Вы писали:

G>Тоесть тут не будет у него ни UB ни исключения, а обе Foo получат что хотели. Я тут вижу одну нерешаемую проблему с безвозвратной утерей сигнатуры функции. Соответственно вызов оператора компилятору просто не сгенерироть.


G>Я чего-то не знаю?


Ну к примеру конструктор Connector может быть шаблонным и далее создавать вспомогательный объект, тип которого зависит от сигнатуры аргумента.

Собственно всякие boost::function именно так и работают, единственно они требуют одинаковой сигнатуры оборачиваемых функций и функторов. Но в своем классе можно сделать как угодно...
Re[2]: Реальность такого делегата
От: glap  
Дата: 22.02.13 10:03
Оценка:
Здравствуйте, Кодт, Вы писали:

К>
К>class Connector
К>{
К>  boost::function< void(boost::any) > fun;

К>  template<class Arg>
К>  static void fun_impl(void(*f)(Arg), boost::any a)
К>  {
К>    if(Arg* v = boost::any_cast<Arg>(&a))
К>      f(*v);
К>    else
К>      f(Arg()); // или любое другое предпочитаемое поведение - не вызывать, кинуть исключение, etc...
К>  }

К>public:
К>  template<class Arg>
К>  Connector( void(*f)(Arg) ) : fun( boost::bind(fun_impl<Arg>, f, _1) ) {}

К>  void operator()(boost::any a) const { fun(a); }
К>};
К>


Ну так в моём примере это не сработает. operator int() не будет вызван.
any_cast сравнит имя типа входного параметра функции (int) и имя типа аргумента (SomeStruct) и выдаст NULL.
Re[2]: Реальность такого делегата
От: glap  
Дата: 22.02.13 10:05
Оценка:
Обе Foo хотели поведения будто мы вызываем их напрямую.
Re[2]: Реальность такого делегата
От: glap  
Дата: 22.02.13 10:07
Оценка:
Здравствуйте, enji, Вы писали:

E>Собственно всякие boost::function именно так и работают, единственно они требуют одинаковой сигнатуры оборачиваемых функций и функторов. Но в своем классе можно сделать как угодно...


Речь о конкретном примере. Должен быть вызван конструктор копирования при входе в Foo2, а при входе в Foo1 должен быть вызван operator int();
Re: Реальность такого делегата
От: rg45 СССР  
Дата: 22.02.13 12:16
Оценка:
Здравствуйте, glap, Вы писали:

G>Один товарищ утверждает, что написал делегат который абстрагируется от сигнатуры функции и способен переварить нормально такой код:

G>
  Скрытый текст
G>
G>struct SomeStruct
G>{
G>    operator int() { return 0; }
G>}

G>void Caller(Connector con)
G>{
G>    con( SomeStruct() );
G>}

G>void Foo1(int i) {}
G>void Foo2(SomeStruct ss) {}

G>Connector con1 = &Foo1;
G>Caller(con1);

G>Connector con2 = &Foo2;
G>Caller(con2);
G>




G>Тоесть тут не будет у него ни UB ни исключения, а обе Foo получат что хотели. Я тут вижу одну нерешаемую проблему с безвозвратной утерей сигнатуры функции. Соответственно вызов оператора компилятору просто не сгенерироть.


G>Я чего-то не знаю?


Пользуясь новшествами языка, это можно сделать не только для разных типов параметров, но и для произвольного их количества, а также для использования с лямбдами:
  Реализация класса Connector
class ConnectorImplBase
{
public:
   virtual ~ConnectorImplBase() { }
};

template<typename... Args>
struct ConnectorImpl : public ConnectorImplBase
{
public: 

   typedef std::function<void(Args...)> CallBack;
   
   explicit ConnectorImpl(const CallBack& callBack) : m_callBack(callBack) { }
   
   void doCall(Args... args)
   {
      m_callBack(args...);   
   }

private:
   CallBack m_callBack;
};

class Connector
{
public:   

   template<typename... Args>
   Connector(const std::function<void(Args...)>& callBack)
   : m(new ConnectorImpl<Args...>(callBack)) { }

   template<typename... Args>
   Connector(void(*callBack)(Args...)) 
   : Connector(std::function<void(Args...)>(callBack)) { }

   template<typename... Args>
   void operator()(Args... args) const
   {
      typedef ConnectorImpl<Args...> Impl;
      Impl* impl = dynamic_cast<Impl*>(m.get()); 
      if (!impl)
         throw std::invalid_argument("Formal and actual parameters missmatch");

      impl->doCall(args...);
   }
private:
   std::shared_ptr<ConnectorImplBase> m;
};

Но у этой реализации есть одно ограничение: типы и количество формальных и фактических параметров должны соответствовать друг другу без неявных преобразований. Несоответствие, если таковое возникает, обнаруживается во время исполнения с выбросом исключения. Это означает, что обращение к Foo1 из твоего примера закончится ошибкой, в то время как Foo2 отработает нормально. Думаю, что реализовать передачу фактических параметров с автоматическим применением неявных преобразований в рамках текущих возможностей языка вряд ли получится.

Вот мой рабочий пример.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[2]: Реальность такого делегата
От: glap  
Дата: 22.02.13 13:23
Оценка:
Вопрос был довольно конкретный. Я указал код. Про обычные делегаты коих у каждого по три велосипеда на каждого я знаю.
Просто товарищ убеждает, что приведённый мною код делает вызов оператора преобразования.
Re: Реальность такого делегата
От: Константин Россия  
Дата: 22.02.13 14:02
Оценка:
Здравствуйте, glap, Вы писали:

G>Один товарищ утверждает, что написал делегат который абстрагируется от сигнатуры функции и способен переварить нормально такой код:

G>
G>void Foo1(int i) {}
G>void Foo2(SomeStruct ss) {}

G>Connector con1 = &Foo1;
G>Caller(con1);

G>Connector con2 = &Foo2;
G>Caller(con2);
G>

G>Я чего-то не знаю?

Делегат работает только с фиксированным конечным набором сигнатур, или претендует на всеобщность?
Re[2]: Реальность такого делегата
От: glap  
Дата: 22.02.13 14:15
Оценка:
Здравствуйте, Константин, Вы писали:

К>Делегат работает только с фиксированным конечным набором сигнатур, или претендует на всеобщность?


Претендует с его слов...
С конечным списком типов аргументов понятно как сделать, а вот с произвольным очевидно, что это невозможно.
Но решил себя перепроверить.
Re[3]: Реальность такого делегата
От: rg45 СССР  
Дата: 22.02.13 15:38
Оценка:
Здравствуйте, glap, Вы писали:

G>Вопрос был довольно конкретный. Я указал код. Про обычные делегаты коих у каждого по три велосипеда на каждого я знаю.

G>Просто товарищ убеждает, что приведённый мною код делает вызов оператора преобразования.

Думаю, товарищ чего-то недоговаривает.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[2]: Реальность такого делегата
От: johny5 Новая Зеландия
Дата: 22.02.13 16:22
Оценка: +1
Здравствуйте, Кодт, Вы писали:

К>Здравствуйте, glap, Вы писали:


G>>Один товарищ утверждает, что написал делегат который абстрагируется от сигнатуры функции и способен переварить нормально такой код:


К>Ну, это дело нехитрое. Ниже покажу, как.


G>>Тоесть тут не будет у него ни UB ни исключения, а обе Foo получат что хотели.


К>А что хотели обе Foo? Могу предположить, что они хотели значение по умолчанию.


G>> Я тут вижу одну нерешаемую проблему с безвозвратной утерей сигнатуры функции. Соответственно вызов оператора компилятору просто не сгенерироть.


К>
К>


Что то ты тут перемудрил. Восстанавливать сигнатуру конечно не надо, а просто организовать нормальный вызов Foo с аргументом.
struct Connector
{
    template<typename F>
    Connector(F functor)
    {
        // универсальный клей
        func = boost::bind<void>( functor, _1 );
    }

    void operator()(SomeStruct s) { return func(s); }

    boost::function<void (SomeStruct s)>  func;
};
Re[3]: Реальность такого делегата
От: glap  
Дата: 22.02.13 16:42
Оценка:
Здравствуйте, johny5, Вы писали:

J>Что то ты тут перемудрил. Восстанавливать сигнатуру конечно не надо, а просто организовать нормальный вызов Foo с аргументом.

J>
J>struct Connector
J>{
J>    template<typename F>
J>    Connector(F functor)
J>    {
J>        // универсальный клей
J>        func = boost::bind<void>( functor, _1 );
J>    }

J>    void operator()(SomeStruct s) { return func(s); }

J>    boost::function<void (SomeStruct s)>  func;
J>};

J>


Ну если уж упрощать поступим самый джидайским методом:

SomeStruct s;
Foo1(s);
Foo2(s);


Только боюсь, как и твой код, это не имеет никакого отношения к озвученной проблеме.
Re[4]: Реальность такого делегата
От: johny5 Новая Зеландия
Дата: 22.02.13 17:10
Оценка:
Здравствуйте, glap, Вы писали:

G>Здравствуйте, johny5, Вы писали:


G>Ну если уж упрощать поступим самый джидайским методом:


G>
G>SomeStruct s;
G>Foo1(s);
G>Foo2(s);
G>


G>Только боюсь, как и твой код, это не имеет никакого отношения к озвученной проблеме.


Вообще то оно собирается и даёт (вроде как ожидаемый вами) результат:
Caller(con1); -> Foo1( (int)SomeStruct() );

Caller(con2); -> Foo2( SomeStruct() );
Re[5]: Реальность такого делегата
От: glap  
Дата: 22.02.13 17:18
Оценка:
Здравствуйте, johny5, Вы писали:

Отлично. Но вопрос был не об этом. Тут речь об универсальных делегатах.
Re[4]: Реальность такого делегата
От: Кодт Россия  
Дата: 22.02.13 17:19
Оценка: 1 (1)
Здравствуйте, glap, Вы писали:

G>Ну если уж упрощать поступим самый джидайским методом:


Ну это не джедайство, а самообман. У jonny5, по крайней мере, было запоминание функции.

G>Только боюсь, как и твой код, это не имеет никакого отношения к озвученной проблеме.


Проблема в том, что проблема так и не была озвучена.
Можно только догадываться, что
— мы запоминаем любую функцию с сигнатурой void(T), где T произвольно
— затем вызываем с аргументом U, где U произвольно
— и если существует неявное приведение U к T, то оно будет сделано

В С++ невозможно сделать двухфазно конкретизируемый (шаблонный) код. А он нам как раз и потребуется, если мы хотим приводить всё ко всему, посредством static_cast, явного или неявного.

Если мы можем зафиксировать множество ожидаемых типов U, то в момент запоминания void(T) сразу породим для каждого ожидаемого U нужные переходники.
Если введём ряд ограничений на свойства типов T и U, — например, запрет на пользовательские операторы приведения, либо разрешение строго определённых приведений (через void*, к примеру), — то тоже сможем.
В яве дженерики или в хаскеле GADT'ы работают, потому что там бинарное представление у всех типов одинаковое: указатель на неизвестно что. В плюсах, ясное дело, такой номер в общем случае не пройдёт.
Перекуём баги на фичи!
Re[5]: Реальность такого делегата
От: glap  
Дата: 22.02.13 17:39
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Проблема в том, что проблема так и не была озвучена.


Может я плохо формулирую, но всё что ты написал я знал. Велосипедов уже понаделал на подобные темы уйму.
У меня просто был вчера диалог с один "пациентом" который уверял меня, что его универсальный абстрагированный
делегат умеет правильно пройтись по всем веткам кода, который я привёл. То есть повести себя будто вызов функций идёт
напрямую с передачей SomeStruct. Убеждал он меня долго поэтому я начал сомневаться в своей адекватности, а не только в его.
Решил себя перепроверить, быть может что-то с языком произошло, что я пропустил или я просто идиот.

Я же не спросил как заставить этот код работать. Речь про "выдуманный" делегат и возможно ли его существование. Заголовок темы так и звучит.
Но видимо не стоит настолько в себе сомневаться и создавать такие темы.
Re[5]: Реальность такого делегата
От: johny5 Новая Зеландия
Дата: 22.02.13 17:40
Оценка:
Здравствуйте, Кодт, Вы писали:

К>- мы запоминаем любую функцию с сигнатурой void(T), где T произвольно

К>- затем вызываем с аргументом U, где U произвольно
К>- и если существует неявное приведение U к T, то оно будет сделано

К>В С++ невозможно сделать двухфазно конкретизируемый (шаблонный) код. А он нам как раз и потребуется, если мы хотим приводить всё ко всему, посредством static_cast, явного или неявного.


В этом случае можно будет один из набор T или U типов сложить в boost::variant или boost::any, как и предлагал.
Ещё можно будет использовать "virtual template". Список типов с которым будет вызываться код всё равно будет ограничен и его можно перечислить:
struct ICallee
{
  virtual void operator()(int ) = 0;
  virtual void operator()(SomeStruct ) = 0;
};

template<typename T>
struct ExactCallee : ICallee
{
   boost::function< void(T> > func;
   ...
};

struct Connector
{
    ICallee* c;

    template<typename F>
    Connector(F functor)
    {
        // extract arg's type
        c = new ExactCallee< mpl::at_c<parameter_types<F>, 1> (functor);
    }

    template<typename U>
    void operator()(U arg)
    {
        c->operator()(arg);
    }
};
Re[6]: Реальность такого делегата
От: Кодт Россия  
Дата: 22.02.13 19:49
Оценка: +1
Здравствуйте, johny5, Вы писали:

К>>- мы запоминаем любую функцию с сигнатурой void(T), где T произвольно

К>>- затем вызываем с аргументом U, где U произвольно
К>>- и если существует неявное приведение U к T, то оно будет сделано

К>>В С++ невозможно сделать двухфазно конкретизируемый (шаблонный) код. А он нам как раз и потребуется, если мы хотим приводить всё ко всему, посредством static_cast, явного или неявного.


J>В этом случае можно будет один из набор T или U типов сложить в boost::variant или boost::any, как и предлагал.

J>Ещё можно будет использовать "virtual template". Список типов с которым будет вызываться код всё равно будет ограничен и его можно перечислить:

Это всё работает только в том случае, когда присвоенный T совпадает с вызываемым U, либо способ приведения T к U заранее известен и проходит через точку стирания типа (dynamic_cast или boost::any — у каждого своя крутизна и свои ограничения).
А вот с пользовательским приведением такой номер не прокатит.

Про boost::variant — это то, о чём я написал выше: мы делаем заготовки для приведения всех типов из набора.
Перекуём баги на фичи!
Re[6]: Реальность такого делегата
От: Vamp Россия  
Дата: 22.02.13 20:57
Оценка:
G>Убеждал он меня долго поэтому я начал сомневаться в своей адекватности, а не только в его.
Безотносительно сути проблемы — к чему схоластика? Если ты считаешь, что его код не работает на каком-то примере — скомпилируй и посмотри, чего проще то? Зачем считать ангелов на острие иглы?
Да здравствует мыло душистое и веревка пушистая.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.