Неявный вывод типа шаблона с двумя аргументами разных типов
Предположим, что следующая ситуация:
Введите A
и введите B
, B
можно неявно преобразовать в A
, но противоположное неверно.
У меня есть функция
template<class T>
void do_stuff(T a, T b);
Я хочу вызвать указанную функцию как таковую:
do_stuff(A{}, B{});
Проблема заключается в том, что компилятор не может вывести тип и вместо этого говорит:
template argument deduction/substitution failed
Я могу вызвать свою функцию следующим образом:
do_stuff<A>(A{}, B{});
Но это более раздражает пользователя.
В качестве альтернативы я могу сделать что-то вроде этого:
template<class T, class M>
void do_stuff(T a, M b);
Но тогда b продолжает веселиться, чтобы быть типа B (с предыдущим вызовом).
В идеале мне бы хотелось что-то вроде:
template<class T, class M = T>
void do_stuff(T a, M b);
Или:
template<class [email protected] MAGIC SO THAT T IS DEDUCED AS BEING THE TYPE OF ARGUMENT NR [email protected]>
void do_stuff(T a, T b);
Возможно ли это?
Ответы
Ответ 1
Оберните b
в невыводимом контексте. Таким образом, будет выведено только a
и b
должен быть преобразован в этот тип.
template <class T> struct dont_deduce { using type = T; };
template <class T> using dont_deduce_t = typename dont_deduce<T>::type;
template<class T>
void do_stuff(T a, dont_deduce_t<T> b);
Ответ 2
Это, конечно, возможно, просто с небольшой делегацией. Вы сделали проблему довольно простой, указав, что вы всегда хотите, чтобы выводимый тип являлся типом первого аргумента, поэтому все, что нам нужно сделать, это немного добавить подсказку к компилятору.
template <class T>
void do_stuff_impl(T a, T b) {
cout << "Doing some work..." << endl;
}
template <class T, class S>
void do_stuff(T a, S b) {
do_stuff_impl<T>(a, b);
}
Теперь пользователь может вызвать do_stuff
с любыми двумя аргументами, а С++ попытается неявно включить второй аргумент в соответствие с типом первого. Если листинг недействителен, вы получите ошибку создания шаблона. В GCC говорится: cannot convert ‘b’ (type ‘A’) to type ‘B’
, что довольно точно и точно. И любой компилятор, заслуживающий своей соли, сможет встроить этот делегированный вызов, поэтому должны быть незначительные накладные расходы.
Ответ 3
В С++ есть ответ: std::common_type
http://en.cppreference.com/w/cpp/types/common_type
template<typename A>
void f_impl(A a, A b)
{
}
template<typename A, typename B>
void f(A a, B b)
{
f_impl<typename std::common_type<A, B>::type>(a, b);
}
struct Z
{
};
struct W
{
operator Z();
};
int main()
{
f(1u, 1l); //work
f(W{}, Z{});
f(Z{}, W{}); //and this work too
}
https://godbolt.org/g/ieuHTS
Ответ 4
другой способ, стремящийся выразить намерение декларативно:
#include <type_traits>
// a B
struct B{};
// an A can be constructed from a B
struct A{
A() {};
A(B) {};
};
// prove that A is constructible from B
static_assert(std::is_convertible<B, A>::value, "");
// enable this function only if a U is convertible to a T
template<
// introduce the actors
class T, class U,
// declare intent
std::enable_if_t<std::is_convertible<U, T>::value>* = nullptr
>
void do_stuff(T, U)
{
}
int main()
{
// legal
do_stuff(A{}, B{});
// does not compile
// do_stuff(B{}, A{});
}
обновление:
для принудительного преобразования можно использовать лямбда:
// enable this function only if a U is convertible to a T
template<class T, class U,
std::enable_if_t<std::is_convertible<U, T>::value>* = nullptr
>
void do_stuff(T a, U b)
{
return[](T& a, T b) -> decltype(auto)
{
}(a, b);
}