I
I
iihaarr2022-04-11 10:35:10
C++ / C#
iihaarr, 2022-04-11 10:35:10

How to disable function definition through templates?

There is a class:

template<const std::size_t rows, const std::size_t columns>
class Matrix 
{
...
}

There is a feature: How to disable this feature for all cases where rows != columns? Wrote this thread:
int GetDeterminant() const noexcept
template<const std::size_t T, const std::size_t U>
struct are_values_equal
{
    constexpr static bool value = T == U;
};

And tried to turn it off like this:
template<typename std::enable_if<are_values_equal<rows, columns>::value>::type = true>

But it gives an error: a non-type template parameter cannot have type

Answer the question

In order to leave comments, you need to log in

1 answer(s)
E
Evgeny Shatunov, 2022-04-11
@iihaarr

It's not as important to simply enable or disable a method based on type template arguments as it is important to explain to the user of the type why a method is available there and not here.
First of all, it is worth understanding SFINAE , because it is this mechanism that allows you to "turn off" definitions in the code under special conditions.
Template inference failure should not result in a code translation error. This is a delicate mechanism that always balances on the edge between translation and error. When you want to turn off the definition of a function, you always need to provide an alternative for it under different conditions.
In this regard, it is very important to separate the template output of the matrix itself and the template output of the matrix methods.
It is the attempts to derive matrix method templates that should fail. In the most literal sense, the method will turn off for a non-square matrix because it could not be deduced from the template.

And here's what it should look like ideally.
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;
  }
};

It is important to ensure that the SFINAE expression is dependent on the method template parameters. Therefore, the template declaration looks like this, and not something else.
Now, what happens if GetDeterminantit was not possible to withdraw from the template? There will be a translation error saying that the method was not found. It doesn't tell the user anything. This simply instructs the translator to stop broadcasting. The user, especially if he is not tempted to know about SFINAE, will not be able to understand the cause of the translation error. Such a situation creates the risk of wasting time investigating the causes of the error.
In the end, it turns out that the matrix is ​​simply not square.
The user needs to explain the reason for the absence of the method.
Is there an easy way to do this.
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;
  }
};

There should always be an alternative output path. Its absence will either result in a translation error or make it difficult for the user to understand. In this case, the explicitly removed method should nudge the user to the right reason. The error message will say that the user is trying to use a remote GetDeterminant.
But there is a way to get away from all these complications altogether and convey the essence of his mistake to the user in an extremely intelligible way.
The fact is that the methods of a type derived from a template are derived as they are used. If no one took the determinant of the matrix, then the method of taking the determinant for it will not be derived.
And if the matrix is ​​not square, the very attempt to derive the method of taking the determinant should be stopped.
And it's extremely easy to do.
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 is not needed at all in resolving the issue. It will only complicate the design more and will confuse.
A simple static statement with the most detailed explanation for the user is sufficient.
For all square matrices, the determinant method will be derived without problems. For any non-square matrix, the derivation of the determinant method will fail on the static assertion test, and the user will get the most specific reason for the failure.

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question