I'm using SFINAE in the style of this answer in order to call a generic vector object by using an appropriate member function. For example, the following code calls operator[](int) const
first, and if that doesn't exist then operator()(int) const
:
template<int I> struct rank : rank<I-1> { static_assert(I > 0, ""); };
template<> struct rank<0> {};
template<typename VectorType>
struct VectorWrapper
{
auto get(int i) const
{
return get(v, i, rank<5>());
}
template<typename V, typename = std::enable_if_t<has_bracket_operator<const V>::value> >
auto get(V const& v, int i, rank<2>) const
{
return v[i];
}
template<typename V, typename = std::enable_if_t<has_parenthesis_operator<const V>::value> >
auto get(V const& v, int i, rank<1>) const
{
return v(i);
}
VectorType v;
};
With the has_bracket_operator
and has_parenthesis_operator
traits set up as suggested in this thread, the whole compiles and seems to work.
However, passing the member vector to the overloaded class templates seems unnecessary from the first, so I tried to set up the same without passing it. For this, I replaced the template parameter V
with the VectorType
parameter used to set up the class template:
template<typename = std::enable_if_t<has_bracket_operator<VectorType>::value> >
auto get(int i, rank<2>) const
{
return v[i];
}
template<typename = std::enable_if_t<has_parenthesis_operator<VectorType>::value> >
auto get(int i, rank<1>) const
{
return v(i);
}
Now, however, the compilation fails (in gcc 5.1.0) with the following error message:
/usr/local/include/c++/5.1.0/type_traits: In substitution of 'template<bool _Cond, class _Tp> using enable_if_t = typename std::enable_if::type [with bool _Cond = has_parenthesis_operator<std::vector<int> >::value; _Tp = void]':
main.cpp:46:10: required from 'struct VectorWrapper<std::vector<int> >'
main.cpp:59:38: required from here
/usr/local/include/c++/5.1.0/type_traits:2388:61: error: no type named 'type' in 'struct std::enable_if<false, void>'
using enable_if_t = typename enable_if<_Cond, _Tp>::type;
Questions:
- What is the reason for this compilation error?
- Is there an appropriate workaround other than that of my first code block? (That is, one that retains the usual coding style -- where one does not have to pass members).
SFINAE comes to us from [temp.deduct]/8, emphasis mine:
The immediate context is what's in the template declaration. In your initial example:
V
is in the immediate context, so a substitution failure on theenable_if
is just a deduction failure.However, in your second example:
VectorType
is not in the immediate context ofget
, so a failure here would not be a deduction failure, it would be a hard error.Unless
VectorType
happens to have all of these operators.The solution to any template problem is to just add more template. In this case, force
VectorType
to be in the immediate context by introducing another type:And call
get<>()
.Or you can just use tag-dispatching:
demo
In your failing example, the template parameter
VectorType
has already been determined by the time get is being resolved. To make SFINAE work, you need to make the template parameters you are using for SFINAE resolve at that method call. The following is a modification of your first example to work like you want to:This way,
V
is resolved whenget
is called, and it will correctly use SFINAE.