Как устранить неоднозначность в перегруженных функциях с помощью SFINAE

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

template<class T>
auto translate_point(T &p, int x, int y) -> decltype(p.x, p.y, void())
{
    p.x += x;
    p.y += y;
}

template<class T>
auto translate_point(T &p, int x, int y) -> decltype(p[0], void())
{
    p[0] += x;
    p[1] += y;
}

translate_point будет работать с точками, которые имеют публичныеx а такжеy члены, и он также будет работать с кортежами / индексируемыми контейнерами, гдеx а такжеy представлены первым и вторым элементом соответственно.

Проблема в том, что другая библиотека определяет класс точек с открытымx а такжеy, но также позволяет индексировать:

struct StupidPoint
{
    int x, y;

    int operator[](int i) const
    {
        if(i == 0) return x;
        else if(i == 1) return y;
        else throw "you're terrible";
    }

};

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

int main(int argc, char **argv)
{
    StupidPoint stupid { 8, 3 };
    translate_point(stupid, 5, 2);
    return EXIT_SUCCESS;
}

но это делает GCC (и лязг) несчастным:

error: call of overloaded ‘translate_point(StupidPoint&, int, int)’ is ambiguous

Теперь я понимаю, почему это происходит, но я хочу знать, как это исправить (при условии, что я не могу изменить внутреннюю часть StupidPoint), и, если нет простого обходного пути, как я мог бы, как разработчик библиотеки, сделать это проще иметь дело с.

 Mattia F.09 июл. 2016 г., 17:01
Какой из них вы хотите позвонить в этом случае?
 jaymmer10 июл. 2016 г., 03:51
В этом случае первая версия, поскольку она (по крайней мере, в теории) немного быстрее. Также,operator[] также имеет неконстантную перегрузку, возвращающуюint&, который я пропустил в моем простом тестовом примере.

Ответы на вопрос(4)

Вы могли бы обеспечить перегрузку дляStupidPoint:

auto translate_point(StupidPoint &p, int x, int y)
{
    p.x += x;
    p.y += y;
}

живой пример

Другое решение:

посколькуoperator[] const дляStupidPointВы можете проверить это в своем состоянии SFINAE:

template<class T>
auto translate_point(T &p, int x, int y) -> decltype(p[0] += 0, void())
{
    p[0] += x;
    p[1] += y;
}

живой пример

Вы также можете использовать другой подход, основанный на типовых особенностях, чтобы выбрать подходящийtranslate_point функция:

template<typename T, typename = void>
struct has_x_y : std::false_type { };

template<typename T>
struct has_x_y<T, decltype(std::declval<T>().x, std::declval<T>().y, void())> : std::true_type { };

template<typename T, typename = void>
struct has_index : std::false_type { };

template<typename T>
struct has_index<T, decltype(std::declval<T>().operator[](0), void())> : std::true_type { };

template<class T>
std::enable_if_t<has_x_y<T>::value> translate_point(T &p, int x, int y)
{
    p.x += x;
    p.y += y;
}

template<class T>
std::enable_if_t<!has_x_y<T>::value && has_index<T>::value> translate_point(T &p, int x, int y)
{
    p[0] += x;
    p[1] += y;
}

живой пример

С SFINAE я бы сделал что-то вроде этого:

template<class T, bool>
auto translate_point(T &p, int x, int y) -> decltype(p[0], void())
{
    p[0] += x;
    p[1] += y;
}

template<class T, bool = std::is_base_of<StupidPoint, T>::value>
auto translate_point(T &p, int x, int y) -> decltype(p.x, p.y, void())
{
    p.x += x;
    p.y += y;
}

Делая это, когда T = (класс сStupidPoint как базовый класс), будет вызвана вторая перегрузка.

Но это проще с простой перегрузкой, как указаноМиз.

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

template<class T>
auto translate_point(T &p, int x, int y) -> decltype(p.x += x, p.y += y, void()) { ... };

template<class T>
auto translate_point(T &p, int x, int y) -> decltype(p[1] += y, void()) { ... } // prefer checking 1, so you don't allow an operator[] that takes a pointer

Сейчас еслиoperator[] вернулint& напротив, это все равно будет неоднозначным. В этом случае вам потребуется способ упорядочить две перегрузки (возможно, с дополнительным аргументом, который либоint или же...?) или просто запретить этот случай. Это отдельное дизайнерское решение.

Решение Вопроса

Если вы хотите отдать приоритет делу, имеющемуx/y, вы можете сделать это:

template<class T>
auto translate_point_impl(int, T &p, int x, int y) -> decltype(p.x, p.y, void())
{
    p.x += x;
    p.y += y;
}

template<class T>
auto translate_point_impl(char, T &p, int x, int y) -> decltype(p[0], void())
{
    p[0] += x;
    p[1] += y;
}

template<class T>
void translate_point(T &p, int x, int y) {
    translate_point_impl(0, p, x, y);
}

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

Если у вас есть три или более вариантов (говоритN), вы можете использовать трюк на основе шаблонов.
Вот пример, приведенный выше, когда-то переключенный на такую ​​структуру:

template<std::size_t N>
struct choice: choice<N-1> {};

template<>
struct choice<0> {};

template<class T>
auto translate_point_impl(choice<1>, T &p, int x, int y) -> decltype(p.x, p.y, void()) {
    p.x += x; p.y += y;
}

template<class T>
auto translate_point_impl(choice<0>, T &p, int x, int y) -> decltype(p[0], void()) {
    p[0] += x;
    p[1] += y;
}

template<class T>
void translate_point(T &p, int x, int y) {
    // use choice<N> as first argument
    translate_point_impl(choice<1>{}, p, x, y);
}

Как видите, сейчасN может принять любое значение.

 Chris Beck10 июл. 2016 г., 04:24
Я этого никогда не виделdecltype(void()) раньше, это хорошо. Это странно для меня, однако, это «по умолчанию создание пустоты» здесь? Приятно, что это работает.
 skypjack10 июл. 2016 г., 08:54
@jaymmer Добавил новый раздел к ответу. Дайте мне знать, если у вас есть сомнения.
 namezero14 мар. 2019 г., 18:05
+1 за трюк с тегом. Это очень интересный способ сделать интуитивно понятный выбор разрешения перегрузки на основе <0> <1> и т. Д.
 skypjack10 июл. 2016 г., 07:07
@jaymmer Да, вы можете использовать шаблонный класс для разрешения перегрузки. Вы хотите, чтобы я добавил пример к ответу?
 jaymmer10 июл. 2016 г., 08:17
это было бы здорово, я не уверен, что знаю, что вы имеете в виду - вы говорите о чем-то отличном от того, что м.с. предложил?
 jaymmer10 июл. 2016 г., 03:45
это на самом деле очень умно, но есть ли способ сделать это, если у вас есть 3 (или более) вариантов на выбор?

Ваш ответ на вопрос