Не так важно просто включить или выключить метод исходя из аргументов шаблона типа, как важно объяснить пользователю типа, почему там метод доступен, а тут - нет.
Для начала стоит разобраться со
SFINAE, ведь именно этот механизм позволяет "выключать" определения в коде при особых условиях.
Провал вывода шаблона не должен приводить к ошибке трансляции кода. Это - тонкий механизм, который всегда балансирует на грани между трансляцией и ошибкой. Когда нужно выключить определение функции, для нее всегда нужно предоставить альтернативу при иных условиях.
В этой связи очень важно разделять вывод шаблона самой матрицы и вывод шаблонов методов матрицы.
Проваливаться должны именно попытки вывода шаблонов методов матрицы. В самом буквальном смысле, метод будет выключаться для неквадратной матрицы потому что его не удалось вывести из шаблона.
И вот как это должно выглядеть в идеале.template< size_t ROWS, size_t COLUMNS >
struct Matrix final
{
template< size_t R = ROWS, size_t C = COLUMNS >
inline std::enable_if_t<R == C, int> GetDeterminant() const
{
return 0;
}
};
Важно гарантировать зависимость выражения SFINAE от параметров шаблона метода. Поэтому объявление шаблона выглядит именно так, а не как-то еще.
Теперь, что будет если
GetDeterminant
не удалось вывести из шаблона? Будет ошибка трансляции, говорящая о том, что метод не найден. Это ничего не говорит пользователю. Это просто инструктирует транслятор остановить трансляцию. Пользователь, особенно если он не искушен знаниями о SFINAE, не сможет понять причину ошибки трансляции. Такая ситуация создаст риск излишней траты времени на дознание причин ошибки.
Под конец выяснится что матрица просто не квадратная.
Пользователю нужно объяснить причину отсутствия метода.
Есть простой способ сделать это.template< size_t ROWS, size_t COLUMNS >
struct Matrix final
{
template< size_t R = ROWS, size_t C = COLUMNS >
inline std::enable_if_t<R != C, int> GetDeterminant() const = delete;
template< size_t R = ROWS, size_t C = COLUMNS >
inline std::enable_if_t<R == C, int> GetDeterminant() const
{
return 0;
}
};
Альтернативный путь вывода всегда должен быть. Его отсутствие или приведет к ошибке трансляции, или усложнит понимание для пользователя. В данном случае явно удаленный метод должен подтолкнуть пользователя к верной причине. Сообщение об ошибке будет говорить о том, что пользователь пытается использовать удаленный
GetDeterminant
.
Но есть способ вообще уйти от всех этих усложнений и крайне доходчиво донести до пользователя суть его ошибки.
Дело в том, что методы выведенного из шаблона типа выводятся по мере их использования. Если никто не брал детерминант матрицы, то и метод взятия детерминанта для нее выведен не будет.
А если матрица не является квадратной, сама попытка вывода метода взятия детерминанта должна быть пресечена.
И делается это крайне просто.template< size_t ROWS, size_t COLUMNS >
struct Matrix final
{
inline int GetDeterminant() const
{
static_assert( ROWS == COLUMNS, "Matrix should be square to calculate the determinant." );
return 0;
}
};
SFINAE в решении вопроса вообще не нужен. Он только больше усложнит конструкцию и будет запутывать.
Достаточно простого статического утверждения с максимально детальным пояснением для пользователя.
Для всех квадратных матриц метод детерминанта будет выведен без проблем. Для любой неквадратной матрицы вывод метода детерминанта провалится на проверке статического утверждения, а пользователь получит максимально конкретную причину провала.