@iihaarr

Как отключить определение функции через шаблоны?

Есть класс:
template<const std::size_t rows, const std::size_t columns>
class Matrix 
{
...
}

Есть функция:
int GetDeterminant() const noexcept
Как отключить эту функцию для всех случаев, когда rows != columns?
Написал такой трейт:
template<const std::size_t T, const std::size_t U>
struct are_values_equal
{
    constexpr static bool value = T == U;
};

И пытался отключить так:
template<typename std::enable_if<are_values_equal<rows, columns>::value>::type = true>

Но выдает ошибку: a non-type template parameter cannot have type
  • Вопрос задан
  • 119 просмотров
Решения вопроса 2
@MarkusD Куратор тега C++
все время мелю чепуху :)
Не так важно просто включить или выключить метод исходя из аргументов шаблона типа, как важно объяснить пользователю типа, почему там метод доступен, а тут - нет.

Для начала стоит разобраться со 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 в решении вопроса вообще не нужен. Он только больше усложнит конструкцию и будет запутывать.
Достаточно простого статического утверждения с максимально детальным пояснением для пользователя.
Для всех квадратных матриц метод детерминанта будет выведен без проблем. Для любой неквадратной матрицы вывод метода детерминанта провалится на проверке статического утверждения, а пользователь получит максимально конкретную причину провала.
Ответ написан
Комментировать
wataru
@wataru Куратор тега C++
Разработчик на С++, экс-олимпиадник.
Можно сделать так:

template <int a, int b>
class A {
    public:
    template<bool tmp = true>
    typename std::enable_if<a==b && tmp, int>::type F() {
       return 1;   
    }
};

...

A<1,1> a;
std::cout << a.F();  // OK.
A<2,100> b;
std::cout << b.F(); // Ошибка A<2,100> не имеет метода F().


Мне сложно объяснить, чтобы было понятно, почему работает именно это, но я попробую.

Во-первых, метод дложен быть обернут в шаблон, потому что если там будет ошибка, то не инстанцируется шаблон для вашего типа Matrix, а он должен быть даже если размерности не равны.

Далее, хотелось бы что бы просто работало вот это:
template<>
 typename std::enable_if<a==b, int>::type F()

Тут все понятно, enable_if не имеет type, если a неравно b. Шаблон никак не инстанциировать и метод не должен был бы генерироваться. Но это было бы слишком просто.

Вместо этого, надо там завести какой-то вообще ненужный как бы параметр шаблона tmp, и обязательно использовать его в enable_if. Это потому что если в этом шаблоне не будет никак использоваться параметр шаблона, то SFINAE не срабатывает, и вылезает ошибка компиляции.
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы