Сбой при выводе типа с указателем на метод-член

У меня есть следующий класс шаблона, который действует как прокси. У него есть метод с именем call который должен использоваться для вызова методов в обернутом объекте. Есть проблема с этим. Вывод типа не удается, и я не могу понять, почему.

Hudsucker::f занимает std::string и тогда не важно, если я передам std::string или const Ссылка на него компилятор может вызвать правильный метод.

Но в случае Hudsucker::g с занимает const ссылка на std::string В обоих случаях вывод типа не выполняется как с GCC, так и с Clang.

Ошибка GCC для первой строки:

main.cpp:36:28: error: no matching function for call to ‘Proxy<Hudsucker>::call(void (Hudsucker::*)(const string&), const string&)’
main.cpp:36:28: note: candidate is:
main.cpp:10:10: note: template<class A> void Proxy::call(void (T::*)(A), A) [with A = A; T = Hudsucker]
main.cpp:10:10: note:   template argument deduction/substitution failed:
main.cpp:36:28: note:   deduced conflicting types for parameter ‘A’ (‘const std::basic_string<char>&’ and ‘std::basic_string<char>’)

Особенно это немного странно no matching function for call to Proxy<Hudsucker>::call(void (Hudsucker::*)(const string&), const string&), Это именно та подпись, которую я ожидаю увидеть на работе.

Ошибка лягушки для первой строки:

main.cpp:36:7: error: no matching member function for call to 'call'
p.call(&Hudsucker::g, s); // <- Compile error
~~^~~~
main.cpp:10:10: note: candidate template ignored: deduced conflicting types for parameter 'A' ('const std::basic_string<char> &' vs. 'std::basic_string<char>')
void call(void (T::*f)(A), A a)

Код:

#include <string>
#include <iostream>

template <typename T> class Proxy
{
public:
Proxy(T &o): o_(o) {}

template <typename A>
void call(void (T::*f)(A), A a)
{
(o_.*f)(a);
}

private:
T &o_;
};

class Hudsucker
{
public:
void f(std::string s) {}
void g(std::string const &s) {}
};

int main()
{
Hudsucker h;
Proxy<Hudsucker> p(h);
std::string const s = "For kids, you know.";
std::string const &r = s;

p.call(&Hudsucker::f, s);
p.call(&Hudsucker::f, r);

p.call(&Hudsucker::g, s); // <- Compile error
p.call(&Hudsucker::g, r); // <- Compile error

return 0;
}

Не могли бы вы объяснить, почему вывод типа не проходит таким образом? Есть ли способ заставить это скомпилировать const Рекомендации?

3

Решение

Компилятор не может определить тип A, поскольку он имеет контрастную информацию. Из типа функции-члена было бы выведено A быть std::string const&в то время как из типа второго аргумента, он будет выводить std::string,

Измените шаблон вашей функции на тот, который допускает разные типы для параметра функции-члена и фактически предоставленного аргумента, а затем SFINAE-ограничивает последний для преобразования в первый:

template <typename A, typename B,
typename std::enable_if<std::is_convertible<B, A>::value>::type* = nullptr>
void call(void (T::*f)(A), B a)
{
(o_.*f)(a);
}

Если вам интересно, почему из этого вызова функции:

std::string const s = "For kids, you know.";
// ...
p.call(&Hudsucker::g, s);

Компилятор выведет std::stringэто из-за пункта 14.8.2.1/2 стандарта C ++ 11:

Если P не является ссылочным типом:

— Если A тип массива, тип указателя, полученный стандартным преобразованием массива в указатель (4.2)
используется вместо A для вычета типа; иначе,

— Если A тип функции, тип указателя, получаемый стандартным преобразованием функции в указатель (4.3)
используется вместо A для вычета типа; иначе,

Если A является cv-квалифицированным типом, cv-квалификаторы верхнего уровня AТип игнорируется для вывода типа.

В цитируемом пункте P твой A (из шаблона вашей функции) и A является std::string const, Это означает, что const в std::string const игнорируется для вывода типа. Чтобы увидеть это лучше, рассмотрим этот более простой пример:

#include <type_traits>

template<typename T>
void foo(T t)
{
// Does NOT fire!
static_assert(std::is_same<T, int>::value, "!");
}

int main()
{
int const x = 42;
foo(x);
}

Учитывая второй вызов функции:

std::string const &r = s;
// ...
p.call(&Hudsucker::g, r);

Причина в том, что тип ID-выражение r является std::string const, Ссылка исключена из-за пункта 5/5:

Если выражение изначально имеет тип «ссылка на T»(8.3.2, 8.5.3), тип корректируется на T до
любой дальнейший анализ
. Выражение обозначает объект или функцию, обозначенную ссылкой, и
Выражение является lvalue или xvalue, в зависимости от выражения.

И теперь мы вернулись к той же ситуации, что и для первого вызова функции.


Как отметил Майк Вайн в комментариях, вы можете захотеть совершенно вперед Ваш второй аргумент при вводе его в качестве аргумента first (функция-член) во время вызова функции:

#include <utility> // For std::forward<>()

template <typename A, typename B,
typename std::enable_if<std::is_convertible<B, A>::value>::type* = nullptr>
void call(void (T::*f)(A), B&& a)
{
(o_.*f)(std::forward<B>(a));
}

Если вы не можете позволить себе C ++ 11, вам не разрешат использовать аргументы по умолчанию для параметров шаблона. В этом случае вы можете использовать SFINAE-ограничение на тип возвращаемого значения:

template <typename A, typename B>
typename enable_if<is_convertible<B, A>::value>::type
//       ^^^^^^^^^ ^^^^^^^^^^^^^^
//       But how about these traits?
call(void (T::*f)(A), B a)
{
(o_.*f)(a);
}

Заметить, что std::enable_if а также std::is_convertible не являются частью стандартной библиотеки C ++ 03. К счастью, Boost имеет свою версию enable_if а также is_convertible, так:

#include <boost/utility/enable_if.hpp>
#include <boost/type_traits/is_convertible.hpp>

template <typename T> class Proxy
{
public:
Proxy(T &o): o_(o) {}

template <typename A, typename B>
typename boost::enable_if<boost::is_convertible<B, A>>::type
call(void (T::*f)(A), B a)
{
(o_.*f)(a);
}

private:
T &o_;
};

Заметить, что boost::enable_if принимает в качестве первого аргумента шаблона тип который определяет value логический член, тогда как std::enable_if принимает логическое значение. Эквивалент std::enable_if в Boost есть boost::enable_if_c,

11

Другие решения

Мне кажется, более простое решение состоит в том, чтобы просто исключить один из двух аргументов из попытки вывести А, а второй является лучшим кандидатом:

template <typename A>
void call(void (T::*f)(A), typename std::identity<A>::type a)
{
(o_.*f)(a);
}

Если у вас нет std::identity в чертах вашего типа используйте это:

template <typename T>
struct identity { typedef T type; };

Вот почему это работает: компилятор не может вывести A из второго аргумента, так как это просто параметр шаблона для чего-то, из чего берется вложенный тип. По сути, он не может сопоставить шаблон любого входящего типа с чем-то _that_contains_A :: type — из-за специализации шаблона он не может перепроектировать аргумент из определения левой стороны. Конечным результатом является то, что вторым аргументом является «недоопределенный контекст». Компилятор не будет пытаться вывести A оттуда.

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

4

Вам просто нужно передать аргумент шаблона в функцию шаблона при вызове его в вашем main.

int main()
{
Hudsucker h;
Proxy<Hudsucker> p(h);
std::string const s = "For kids, you know.";
std::string const &r = s;

p.call(&Hudsucker::f, s);
p.call(&Hudsucker::f, r);

//just add template argument to template function call !!!
p.call< const std::string & > (&Hudsucker::g, s); // <- NO  Compile error !!!!
p.call< const std::string & > (&Hudsucker::g, r); // <- NO Compile error !!!**

return 0;

}

1
По вопросам рекламы [email protected]