Неявный вывод типа шаблона с двумя аргументами разных типов

Предположим, что следующая ситуация:

Введите 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);
}