Home > OS >  Why does this code with SFINAE compiles error, even though there is a template that can match
Why does this code with SFINAE compiles error, even though there is a template that can match

Time:04-19

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.)

CodePudding user response:

Thanks and the question is solved now. The error message is as follows:

cxx.cpp:24:62: error: no member named 'value' in 'vec_size_impl<Vec<int, Const<2>>, Const<6>>'
inline constexpr size_t Vec_size_v = vec_size_impl<T, Type>::value;
                                     ~~~~~~~~~~~~~~~~~~~~~~~~^
cxx.cpp:29:27: note: in instantiation of variable template specialization 'Vec_size_v<Vec<int, Const<2>>, Const<6>>' requested here
    ((0   ...   Sizes) == Vec_size_v<T, Type>),
                          ^
cxx.cpp:46:3: note: while substituting explicitly-specified template arguments into function template 'split'
  split<Vec<int, Const<2>>, Const<6>>(a);
  ^

and the error is because the substitution failure is a hard error, since the deduction of Vec_size_impl::value is not in the immediate context. The question can be solved by replacing the Vec_size_v<T, Type> by Vec_size_impl<T, Type>::value in the enable_if.

CodePudding user response:

SFINAE applies only if the invalid type or expression resulting from a use of a template parameter appears within:

  • a template parameter declaration within the same template parameter list
  • a default template argument which is used (or will be if overload resolution or class partial specialization matching selects the template)
  • for a function template, the declared type of the function (including its function parameter types, return type, or exception specification, but not when deducing a placeholder return type from return statements),
  • for a function template, its explicit(constant-expression) specifier, if any
  • for a class template partial specialization, the template arguments specified after the template name

See [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.

  • Related