SFINAE works as a template function. In the context of pattern type substitution, substitution in the immediate context of substitution is not an error and instead is considered to be a replacement failure.
Please note, however, that there must be a valid replacement or if your program is poorly formed, no diagnostics are required. I suppose that this condition exists so that in the future more "intrusive" or full checks can be added to the language in the future, which check the correctness of the template function. As long as these checks actually verify that a template can be created by some type, it becomes a valid check, but it can break code that expects a template without valid substitutions to be valid, if that makes sense. This can make your original solution a poorly formed program if there is no type of template that you could pass to the operator== function that would allow compilation of the program.
In the second case, there is no substitution context, therefore SFINAE is not applied. There is no replacement for failure.
Recently, I looked at the incoming Concepts proposal, you can add requirements to the methods in the template object, which depend on the template parameters of the object, and if the method fails, it will not be taken into account when overload resolution is enabled. This is really what you want.
There is no standard way to do this in accordance with the current standard. The first attempt is an attempt that people usually make, and it compiles, but it technically violates the standard (but failure diagnosis is not required).
The standards-compliant ways I decided to do what you want:
Change one of the method parameters as a reference to a never completed type if your condition fails. The body of a method is never created unless called, and this method does not allow it to be called.
Using a helper class of the CRTP base class, which uses SFINAE to include / exclude the method depending on an arbitrary condition.
template <class D, class ET, class=void> struct equal_string_helper {}; template <class D, class ET> struct equal_string_helper<D,ET,typename std::enable_if<std::is_same<ET, char>::value>::type> { D const* self() const { return static_cast<D const*>(this); } bool operator==(const std::string & other) const { if (self()->count_ == other.length()) { return memcmp(self()->elements_, other.c_str(), other.length()) == 0; } return false; } };
where do we do it:
template <typename ElementType> class WorkingSimpleVector:equal_string_helper<WorkingSimpleVector,ElementType>
We can reorganize the conditional mechanisms from the implementation of CRTP if we choose:
template<bool, template<class...>class X, class...> struct conditional_apply_t { struct type {}; }; template<template<class...>class X, class...Ts> struct conditional_apply_t<true, X, Ts...> { using type = X<Ts...>; }; template<bool test, template<class...>class X, class...Ts> using conditional_apply=typename conditional_apply_t<test, X, Ts...>::type;
Then we split the CRTP implementation without conditional code:
template <class D> struct equal_string_helper_t { D const* self() const { return static_cast<D const*>(this); } bool operator==(const std::string & other) const { if (self()->count_ == other.length()) { return memcmp(self()->elements_, other.c_str(), other.length()) == 0; } return false; } };
then plug them in:
template<class D, class ET> using equal_string_helper=conditional_apply<std::is_same<ET,char>::value, equal_string_helper_t, D>;
and we use it:
template <typename ElementType> class WorkingSimpleVector: equal_string_helper<WorkingSimpleVector<ElementType>,ElementType>
which looks the same at the point of use. But the mechanism behind was reorganized, so, a bonus?