Есть ли способ указать приоритет среди пользовательских преобразований?

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

Мне нужно определить приоритет пользовательских преобразований, например:

struct X{}
struct Y{}

struct Z{
operator X(){...}
operator Y(){...}
}

void foo(X x){...}
void foo(Y y){...}

// somewhere in some template client code
...{
Z z = ...;
...
foo(z); // WILL NOT COMPILE
}

Это не скомпилируется, так как преобразование из Z либо X или же Y неоднозначно. Есть ли способ решить эту двусмысленность. Т.е. можно ли как-то сказать компилятору: есть ли перегруженная функция для X а также Yтогда предпочитаешь кастовать Z в X вместо того, чтобы не скомпилировать.

Я знаю, что не существует простого способа указать это. Но, возможно, некоторая магия шаблонов и структура шаблона упаковки, о которой я не знаю, могут сработать. Мне разрешено изменять код клиента. Я не могу использовать явное приведение, поскольку клиентский код представляет собой шаблонный код, который не знает о типе Z и доступные перегрузки foo,

4

Решение

Вы можете сделать что-то вроде «приоритетный вызов», с шаблоном, подобным этому:

struct P2 {};
struct P1: P2 {};

template<class A>
void foo(A x, P1, typename std::common_type<X,A>::type* =nullptr)
{ foo(static_cast<X>(x)); }

template<class A>
void foo(A y, P2, typename std::common_type<Y,A>::type* =nullptr)
{ foo(static_cast<Y>(y)); }

template<class A> void foo(A a) { foo(a,P1()); }

Будучи P2 базой для P1 и вызовом, сделанным с P1, если common_type может скомпилироваться, первая версия идет. Если он не может скомпилироваться, первая версия как бы не существует (SFINAE), а вторая выходит. Если он также не может скомпилировать … если A — это просто X или просто Y, то вызывается соответствующий исходный foo, иначе это не может скомпилировать несовместимые типы.

Обратите внимание, что вы можете даже обобщить «приоритет» как

template<size_t N> struct P: P<N+1> {}
template<> struct P<10> {}

объявлять функции SFINAE P<1>, P<2> и т. д. до P<10>и выполните корневой вызов с помощью P<0>()

1

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

Вот небольшая система, которая умно приводит переменную к последовательности типов Ts... такой, что первый элемент в списке Ts... что переменная неявно преобразуется в выбранную:

namespace details {
template<class...>struct types{using type=types;};
template<class U, class Types, class=void>
struct smart_cast_t:std::false_type {
using type=U;
template<class A>
U operator()(A&& a)const{return std::forward<A>(a);}
};
template<class U, class T0, class...Ts>
struct smart_cast_t<
U, types<T0, Ts...>,
typename std::enable_if<std::is_convertible<U, T0>::value>::type
>:std::true_type
{
using type=T0;
template<class A>
T0 operator()(A&& a)const{return std::forward<A>(a);}
};
template<class U, class T0, class...Ts>
struct smart_cast_t<
U, types<T0, Ts...>,
typename std::enable_if<!std::is_convertible<U, T0>::value>::type
>:smart_cast_t< U, types<Ts...> >
{};
}

template<class... Ts, class U>
auto smart_cast( U&& u )
-> decltype(details::smart_cast_t< U, details::types<Ts...> >{}( std::forward<U>(u) ))
{
return details::smart_cast_t< U, details::types<Ts...> >{}( std::forward<U>(u) );
}

Теперь идея заключается в том, что теперь мы можем изменить foo следующее:

void foo_impl(X);
void foo_impl(Y);

template<class A>
void foo(A&& a) {
foo_impl( smart_cast<X, Y>(std::forward<A>(a)) );
}

а также foo бросает A в X если возможно, а если нет Y,

Мы могли бы написать всю систему, в которой мы передаем описание перегрузок foo в упаковке, как types< types<X,Y> > и набор перегрузки foo к некоторому магическому коду, который выплевывает диспетчер, но это было бы по инженерному делу.

живой пример

Это использует функции C ++ 11. С C ++ 14 мы можем отбросить некоторые ошибки smart_cast,

Дизайн довольно прост. Мы создаем types связка для работы со связками типов (просто шаблон).

Тогда наш details::smart_cast_t имеет запасную базовую специализацию, которая просто преобразует наши U в U, Если мы можем преобразовать U к первому типу мы так и делаем. В противном случае, мы возвращаемся к нашему родительскому типу, возможно, заканчивая базовой специализацией.

Я скрываю это в деталях, поэтому наша публичная функция проста — это smart_cast< type1, type2, type3, etc >( expression ), Нам не нужно проходить U тип выражения, как мы его выводим, а затем передать его в детали для работы, которая будет сделана.

Во время выполнения это сводится к одному неявному приведению: все вышеперечисленное делается во время компиляции.

Единственным недостатком является то, что это может привести к предупреждению на некоторых компиляторах, так как мы используем неявное преобразование. Добавить static_cast<T0> к первой неосновной специализации smart_cast_t чтобы избежать этого.

Я унаследовал smart_cast_t от true_type а также false_type без необходимости. Это означает, что значение smart_cast_t<U, types<Ts...>>::value скажет вам, если U преобразован в любой из Ts...или просто остался один как U,

Вызывающий отвечает за категории rvalue vs lvalue. Если приведение не выполнено, возвращается U не U&& если прошло rvalue. Запасной вариант — к U — Я думаю, что будет генерировать лучшие сообщения об ошибках, но если вы предпочитаете это smart_cast<int, double>(std::string("hello")) не скомпилировать вместо того, чтобы возвращать std::stringПросто удалите operator() от базовой специализации smart_cast_t,

Кстати говоря, я тоже не работаю smart_cast<int, double>(""), Может понадобиться немного typename std::decay<U>::typeс или что-то там.

1

Да, приведите это явно:

foo(static_cast<Y>(z));
0
По вопросам рекламы ammmcru@yandex.ru