У меня есть следующий класс шаблона, который действует как прокси. У него есть метод с именем 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
Рекомендации?
Компилятор не может определить тип 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
,
Мне кажется, более простое решение состоит в том, чтобы просто исключить один из двух аргументов из попытки вывести А, а второй является лучшим кандидатом:
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, включая второй аргумент.
Вам просто нужно передать аргумент шаблона в функцию шаблона при вызове его в вашем 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;
}