I'm studying the implementation of the Matrix
class (explained in Stroustrup's book TC++PL 4th ed.), but I can't really understand some passages.
I found this code:
file traits.h -> https://github.com/statslabs/matrix/blob/master/include/slab/matrix/traits.h
file matrix.h -> https://github.com/statslabs/matrix/blob/master/include/slab/matrix/matrix.h
In matrix.h there is a function (as many other) with Enable_if
:
template<typename T, std::size_t N>
template<typename M, typename F>
Enable_if<Matrix_type<M>(), Matrix<T, N> &> Matrix<T, N>::apply(const M &m, F f) {
/// Some code...
}
and I think Enable_if
says: If (M
is a Matrix
), declare the apply's return type as Matrix<T, N>&
.
Then, I want to know how does Matrix_type<M>()
works, so I go to traits.h
, and I read:
struct substitution_failure {};
template <typename T>
struct substitution_succeeded : std::true_type {};
template <>
struct substitution_succeeded<substitution_failure> : std::false_type {};
template <typename M>
struct get_matrix_type_result {
template <typename T, size_t N, typename = Enable_if<(N >= 1)>>
static bool check(const Matrix<T, N> &m);
template <typename T, size_t N, typename = Enable_if<(N >= 1)>>
static bool check(const MatrixRef<T, N> &m);
static substitution_failure check(...);
using type = decltype(check(std::declval<M>()));
};
template <typename T>
struct has_matrix_type
: substitution_succeeded<typename get_matrix_type_result<T>::type> {};
template <typename M>
constexpr bool Has_matrix_type() {
return has_matrix_type<M>::value;
}
template <typename M>
using Matrix_type_result = typename get_matrix_type_result<M>::type;
template <typename M>
constexpr bool Matrix_type() {
return Has_matrix_type<M>();
}
The first 3 structs describe success and failure cases, the template<>
is the specialization of substitution_succeeded
that says: if the type of substitution_succeeded
is substitution_failure
, "return" false else "return" true.
I hope that what I'm saying is correct.
Now, get_matrix_type_result
is completely obscure. I can't understand why it use a variadic function (check(...)
), what are declval
and decltype
doing in this code and how it is possible that check can return a bool or a substitution_failure
. Why not just bool
?
Thanks.
Now, get_matrix_type_result is completely obscure. I can't understand why it use a variadic function (check(...)), what are declval and decltype doing in this code and how it is possible that check can return a bool or a "substitution_failure". Why not just bool?
An important point is that Enable_if
(std::enable_if
starting from C++11) is designed to enable or not enable something (a template function, a template class specialization, a template method, a template variable specialization).
On the ground is a C++ principle named SFINAE (Substitution Failure Is Not An Error) that say that, in some places, if a substitution do not take place, it's only a soft error, non an hard error, and the compilation can continue.
In your case, you have that type
is defined as follows
using type = decltype(check(std::declval<M>()));
where decltype()
"return" the type of the argument; in this case, the type returned from a call to check()
with an hypothetical object of type M
(the template parameter of the class).
You could write
using type = decltype(check(M{}));
to pass an object of type M
. But this works only with types that are default constructible. The question is: how to use, in a decltype()
argument (that has the exclusive function to deduce types, not execute instructions) an object of a generic type if we don't know how construct an object of that type?
The solution is a function only declared (not defined) as follows
template<class T>
typename std::add_rvalue_reference<T>::type declval() noexcept;
This is a trick to have an object of type T
(or better: T &
) also when do you don't know how to construct it.
Returning to check
, you have three version of it (only declared: are used inside a decltype()
; we are only interested in the returned type so there is no need to execute them, so there is no need to define them):
1) the first one accept a Matrix<T, N>
but only (Enable_if
) if N >= 1
template <typename T, size_t N, typename = Enable_if<(N >= 1)>>
static bool check(const Matrix<T, N> &m);
If you call check()
with a Matrix<T, 0>
, the Enable_if
return nothing so you have a substitution failure (defining the default for a template parameter) so this version of check()
isn't enabled
2) the second one accept a MatrixRef<T, N>
but only (Enable_if
) if N >= 1
template <typename T, size_t N, typename = Enable_if<(N >= 1)>>
static bool check(const MatrixRef<T, N> &m);
Again: if you call check()
with a MatrixRef<T, 0>
, the Enable_if
return nothing so you have a substitution failure (defining the default for a template parameter) so this version of check()
isn't enabled
3) the third one accept everything and is ever enabled
static substitution_failure check(...);
Conclusion:
1) if M
is a Matrix<T, N>
(or an object convertible to a Matrix<T, N>
), for some T
and some N
with N >= 1
, the compiler can choose between version (1) and version (3) of check()
and choose version (1) because more specialized, that return bool
, so type
become bool
2) if M
is a MatrixRef<T, N>
(or an object convertible to a MatrixRef<T, N>
), for some T
and some N
with N >= 1
, the compiler can choose between version (2) and version (3) of check()
and choose version (2) because more specialized, that return bool
, so type
become bool
3) if M
isn't convertible to a Matrix<T, N>
or a MatrixRef<T, N>
, with N >= 1
, the compiler can choose only version (3), that return a substitution_failure
, so type
become substitution_failure
.
Off Topic: the code you show us seems to me a little overcomplicated.
By example, if you rewrite get_matrix_type_result
as follows
template <typename M>
struct get_matrix_type_result {
template <typename T, size_t N, typename = Enable_if<(N >= 1)>>
static std::true_type check(const Matrix<T, N> &m);
template <typename T, size_t N, typename = Enable_if<(N >= 1)>>
static std::true_type check(const MatrixRef<T, N> &m);
static std::false_type check(...);
using type = decltype(check(std::declval<M>()));
};
you have that type
is the type that you needs in has_matrix_type
that can be defined as follows
template <typename T>
struct has_matrix_type
: public get_matrix_type_result<T>::type
{ };
avoiding at all substitution_failure
and substitution_succeded
.
But, maybe, the code is written this way for other needs.