Вывод типа шаблона для указателя на функцию-член

У меня проблема, очень похожая на проблему, представленную Морфеусом, в следующем вопросе:

Перегруженный указатель функции-члена на шаблон

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

Рассмотрим этот пример (полученный из исходного вопроса):

template <typename T>
class A
{
public:
  template<class Arg1>
  void connect(void (T::*f)(Arg1)) 
  {
    //Do some stuff
  }

  template<class Arg1, class Arg2>
  void connect(void (T::*f)(Arg1,Arg2)) 
  {
    //Do some stuff
  }

  void connect(void (T::*f)()) 
  {
    //Do some stuff
  }
};

class GApp
{
public:
    void foo() {}
    void foo(double d) {}
    void foo(double d1, double d2) {}
};


int main ()
{
  A<GApp> a;
  a.connect (&GApp::foo);                // foo () - OK
  a.connect<double> (&GApp::foo);        // foo (double) - FAIL
  a.connect<double,double> (&GApp::foo); // foo (double,double) - OK
}

GNU G ++ 3.4.5 и MSVC 2008 не компилируют приведенный выше код, так как оба представляют похожие сообщения об ошибках:

test.cpp: In function `int main()':
test.cpp:36: error: call of overloaded `connect(<unknown type>)' is ambiguous
test.cpp:7: note: candidates are: void A<T>::connect(void (T::*)(Arg1)) [with Arg1 = double, T = GApp]
test3.cpp:13: note:               void A<T>::connect(void (T::*)(Arg1, Arg2)) [with Arg1 = double, Arg2 = double, T = GApp]

Мне известно о некоторых обходных приемах, которые могут заставить его скомпилировать, например, назначении указателя на переменную того же типа (например, void (GApp::*tmp)(double) = &GApp::foo;) или использовании более явной формы при вызове функции подключения (например, connect((void (GApp::*)(double))(&GApp::foo));).

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

Заранее спасибо!


person obelloc    schedule 31.03.2011    source источник
comment
На самом деле я бы побеспокоился о том, чтобы первый не потерпел неудачу. Слишком легко забыть об аргументах шаблона и автоматически выбрать неправильную перегрузку.   -  person UncleBens    schedule 01.04.2011
comment
В вашем main () не компилируется только второй вызов, a.connect ‹double› (& GApp :: foo). Почему вы упомянули также о провале 3-го вызова?   -  person iammilind    schedule 01.04.2011
comment
@iammilind, извини, я просто неправильно понял сообщения об ошибках. Редактировал вопрос. Спасибо.   -  person obelloc    schedule 01.04.2011


Ответы (2)


Для a.connect<double> (&GApp::foo) и foo(double), и foo(double, double) будут соответствовать перегрузке connect с одним и двумя параметрами шаблона соответственно (в случае версии с двумя параметрами будет выведен второй аргумент шаблона, первый аргумент был предоставлен вами явно) .

Если вы хотите устранить неоднозначность случаев, я рекомендую передавать точный тип, чтобы не было никаких сюрпризов. Вместо этих перегрузок, почему бы не использовать одну перегрузку?

template<typename MType, typename T>
void connect(MType T::*f) 
{
  //Do some stuff
}

a.connect<void()> (&GApp::foo);
a.connect<void(double)> (&GApp::foo);
a.connect<void(double, double)> (&GApp::foo);

Последний вызов connect тоже должен работать в вашем коде. Затем вы можете проанализировать тип MType в connect с помощью отдельных шаблонов, чтобы получить тип параметра и возвращаемый тип.

person Johannes Schaub - litb    schedule 31.03.2011
comment
Отлично! На самом деле, мне просто было трудно понять это определение шаблона, я не знал, что можно даже определить такой указатель на функцию (MType T::f*). Тем не менее, почему при использовании этого подхода компилятор не может определить тип указателя функции, даже если нет перегрузок? - person obelloc; 01.04.2011
comment
@NCrawl, он должен уметь выводить. Я думаю, что у MSVC ++ с этим могут быть проблемы. Тогда это было бы ошибкой компилятора. Это определенно действительно C ++. - person Johannes Schaub - litb; 01.04.2011
comment
@litb Хорошо, спасибо вам большое! Для протокола: MSVC'08 не возражал против этого, только GNU GCC 3.4.5 не мог этого сделать. - person obelloc; 01.04.2011
comment
@NCrawl По-видимому, ни msvc9, ни msvc10 не могут скомпилировать пример кода litb (конечно, после его исправления). - person wmamrak; 04.01.2013

Проблема с исходным кодом в том, что вы явно определяете неправильную часть. Когда вы указываете список параметров шаблона, даже если вы указываете один, компилятор попытается вывести второй, а затем он снова вернется к неоднозначности, потому что он не знает, хотите ли вы использовать (double, double) или ( двойная) вариация.

Вместо определения параметров шаблона позвольте компилятору вычислить их, но явно укажите, какую из функций foo () вы хотите использовать:

int main()
{
    A<GApp> a;
    a.connect (&GApp::foo);                                     // foo () - OK
    a.connect( ( void (GApp::*)(double) )&GApp::foo);           // foo (double) - OK
    a.connect( ( void (GApp::*)(double, double) ) &GApp::foo);  // foo (double,double) - OK
}
person DXM    schedule 01.04.2011