Исходный рабочий код
У меня есть шаблон класса с двумя параметрами шаблона и оптимизированным 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, как я сделал?