The code is as follows.
#include <tuple>
#include <array>
template <typename T, typename Type>
struct Vec {
using value_type = T;
static constexpr size_t size() { return Type::size; }
};
template <size_t Size>
struct Const {
static constexpr size_t size = Size;
};
template <class T, class Type, class = void>
struct vec_size_impl {};
template <class T, class Type>
struct vec_size_impl<T, Type, std::enable_if_t<std::is_arithmetic_v<T>>>
: std::integral_constant<size_t, Type::size> {};
template <class T, class Type>
inline constexpr size_t Vec_size_v = vec_size_impl<T, Type>::value;
template<class T, class Type, size_t... Sizes>
// template<size_t... Sizes, class T, class Type>
std::enable_if_t<
((0 + ... + Sizes) == Vec_size_v<T, Type>),
std::tuple<Vec<T, Const<Sizes>>...>
> split(const Vec<T, Type>&) noexcept {
return std::make_tuple<Vec<T, Const<Sizes>>...>();
}
template<class V, class Type>
std::enable_if_t<
(Vec_size_v<typename V::value_type, Type> % V::size() == 0),
std::array<V, Vec_size_v<typename V::value_type, Type> / V::size()>
> split(const Vec<typename V::value_type, Type>&) noexcept {
return std::array<V, Vec_size_v<typename V::value_type, Type> / V::size()>();
}
int main() {
Vec<int, Const<6>> a;
split<Vec<int, Const<2>>, Const<6>>(a);
return 0;
}
Here (I think) the second split()
can be matched, but I still got a compile error for the substitution fail of the first template. What is the reason for this? I have not yet found an entry in the C++ standard that can explain this problem. (This appears to be related to variadic templates, because if I modify the order of the template parameters and put the parameter pack in the first place, the code would compile correctly.)
SFINAE applies only if the invalid type or expression resulting from a use of a template parameter appears within:
explicit(
constant-expression)
specifier, if anySee [temp.deduct]/8 - this is the "immediate context" rule.
Substitution of all type aliases and type alias templates happens essentially "before" template argument substitution, since [temp.alias]/2 says a use of the alias template is always equivalent to its substitution. For example, this explains why SFINAE applies to the ::type
member lookup in a std::enable_if_t
within a function type - it is equivalent to the written-out typedef std::enable_if<
...>::type
, so when this forms an invalid type, it's considered to be in the "immediate context" of the function template argument substitution. Type aliases don't actually get "instantiated" at all like function templates, class templates, and variable templates do.
When overload resolution considers the first split
template, it tries to get the value of Vec_size_v<Vec<int, Const<2>>, Const<6>>
, which causes an implicit instantiation of that specialization of the variable template. The evaluation of that variable template's initializer is within that variable template instantiation, not within the function template's function type, so SFINAE does not apply and the variable template has an error, even though it happened during a template argument deduction for overload resolution.
The obvious workaround, though probably not what you want, is to require the longer Vec_size<T, Type>::value
instead of Vec_size_v<T, Type>
.
Or you could give the primary template for vec_size_impl
a static value
member. But it doesn't actually need to have a numeric type: if you do
template <class T, class Type, class = void>
struct vec_size_impl
{
struct none_t {};
static constexpr none_t value;
};
// partial specialization as before
template <class T, class Type>
inline constexpr auto Vec_size = vec_size_impl<T, Type>::value;
then the same declaration of the first split
would get an actual valid constant expression for its Vec_size_v
use, but an invalid expression (0 + ... + Sizes) == Vec_size_v<T, Type>
since there's no matching operator==
. But this invalid expression is within the function template's function type, so then SFINAE can discard the function template from the overload resolution process.