Скрытые шаблоны друзей в шаблоне класса - PullRequest
2 голосов
/ 20 февраля 2020

Исходный рабочий код

У меня есть шаблон класса с двумя параметрами шаблона и оптимизированным operator==, когда два типа одинаковы и одно условие удовлетворяется. Мой оригинальный код выглядит следующим образом (в демонстрационных целях я делаю обобщенный возврат c false и тот, где T1 == T2 return true):

template<typename T1, typename T2>
struct my_class
{
    // A few hundred LOCs
};

template<typename U1, typename U2>
bool operator==(my_class<U1, U2> const& lhs, my_class<U1, U2> const& rhs)
{
    return false;
}

template<typename U>
auto operator==(my_class<U, U> const& lhs, my_class<U, U> const& rhs)
    -> std::enable_if_t<some_condition, bool>
{
    return true;
}

Идея состоит в том, что первый по умолчанию используется перегрузка operator==, и когда оба типа U1 и U2 имеют одинаковый тип и удовлетворяется some_condition, вторая перегрузка действительна и выбрана в качестве лучшего соответствия.

Проблема

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

Сначала я попробовал наиболее очевидный подход к друзьям: переместить определения, как в шаблоне класса, и добавить к ним префикс friend:

template<typename T1, typename T2>
struct my_class
{
    // A few hundred LOCs

    template<typename U1, typename U2>
    friend bool operator==(my_class<U1, U2> const& lhs, my_class<U1, U2> const& rhs)
    {
        return false;
    }

    template<typename U>
    friend auto operator==(my_class<U, U> const& lhs, my_class<U, U> const& rhs)
        -> std::enable_if_t<some_condition, bool>
    {
        return true;
    }
};

Многое, чтобы не к моему удивлению это не сработало, и я получил ошибки переопределения. Причина объяснения - вместе со стандартной цитатой - в этом ответе .

Попытка

Мы всегда сравниваем my_class с соответствующими параметрами шаблона, поэтому я полагал, что я мог бы избавиться от внутреннего template<typename U1, typename U2> в первом определении, однако во втором его сложнее, так как один параметр шаблона был использован для создания более специализированной перегрузки operator==. Другим решением было бы поместить эту перегрузку в специализацию my_class<T, T>, но, поскольку класс большой, я не чувствовал необходимости дублировать его содержимое, поскольку почти все остальное не изменилось. Возможно, я мог бы ввести еще один уровень косвенности для общего кода, но у меня уже есть огромное количество косвенных указаний.

Если я попытался откатиться на старый добрый SFINAE, чтобы убедиться, что T1 и T2 то же самое:


template<typename T1, typename T2>
struct my_class
{
    // A few hundred LOCs

    friend auto operator==(my_class const& lhs, my_class const& rhs)
        -> bool
    {
        return false;
    }

    friend auto operator==(my_class const& lhs, my_class const& rhs)
        -> std::enable_if_t<std::is_same<T1, T2>::value && some_condition, bool>
    {
        return true;
    }
};

По какой-то причине я не утверждаю, что полностью понимаю, что вторая operator== выше фактически не сформирована, но мы можем обойти это, добавив некоторые дополнительные параметры шаблона по умолчанию в the mix:

template<typename T1, typename T2>
struct my_class
{
    // A few hundred LOCs

    friend auto operator==(my_class const& lhs, my_class const& rhs)
        -> bool
    {
        return false;
    }

    template<typename U1=T1, typename U2=T2>
    friend auto operator==(my_class const& lhs, my_class const& rhs)
        -> std::enable_if_t<std::is_same<U1, U2>::value && some_condition, bool>
    {
        return true;
    }
};

Компилируется, как и ожидалось, но сравнивая два экземпляра my_class с соответствующими T1 и T2, теперь возвращается false, поскольку вторая перегрузка operator== равна меньше специализированных, чем первый. Обоснованное предположение говорит мне, что причиной является новый слой шаблона, поэтому я добавил параметры шаблона к первой перегрузке operator==, а также к условию SFINAE, которое является отрицанием другого, чтобы убедиться, что перегрузка не будет быть неоднозначным с соответствием T1 и T2:

template<typename T1, typename T2>
struct my_class
{
    // A few hundred LOCs

    template<typename U1=T1, typename U2=T2>
    friend auto operator==(my_class const& lhs, my_class const& rhs)
        -> std::enable_if_t<not(std::is_same<U1, U2>::value && some_condition), bool>
    {
        return false;
    }

    template<typename U1=T1, typename U2=T2>
    friend auto operator==(my_class const& lhs, my_class const& rhs)
        -> std::enable_if_t<std::is_same<U1, U2>::value && some_condition, bool>
    {
        return true;
    }
};

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

Вернемся к вопросу, который я изначально хотел задать

Я попытался объяснить мою проблему и примерное решение и то, как я до нее дошел. Мой вопрос: есть ли лучший способ достичь того же результата (скрытые друзья с моим кодом) без необходимости углубляться во все проблемы с шаблонами, которые я выделил выше? Могу ли я иметь таких скрытых друзей, полагаясь на встроенный частичный порядок шаблонов функций, вместо того, чтобы заменить его другим слоем SFINAE, как я сделал?

1 Ответ

2 голосов
/ 20 февраля 2020

Примете ли вы это?

template<typename T1, typename T2>
struct my_class
{
    friend bool operator==(my_class const& lhs, my_class const& rhs)
    {
        if constexpr (std::is_same_v<T1, T2>) {
            return condition;
        } else {
            return false;
        }
    }
};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...