8

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;

DEMO

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).
Community
  • 1
  • 1
davidhigh
  • 14,652
  • 2
  • 44
  • 75
  • *Very* related: [c++ - std::enable_if to conditionally compile a member function - Stack Overflow](https://stackoverflow.com/questions/6972368/stdenable-if-to-conditionally-compile-a-member-function?noredirect=1&lq=1) – user202729 Jan 22 '22 at 14:03

3 Answers3

5

SFINAE comes to us from [temp.deduct]/8, emphasis mine:

If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed, with a diagnostic required, if written using the substituted arguments. [ Note: If no diagnostic is required, the program is still ill-formed. Access checking is done as part of the substitution process. —end note ] Only invalid types and expressions in the immediate context of the function type and its template parameter types can result in a deduction failure.

The immediate context is what's in the template declaration. In your initial example:

template<typename V, typename = std::enable_if_t<has_bracket_operator<const V>::value> >
auto get(V const& v, int i, rank<2>) const

V is in the immediate context, so a substitution failure on the enable_if is just a deduction failure.

However, in your second example:

template<typename = std::enable_if_t<has_bracket_operator<VectorType>::value> >
auto get(int i, rank<2>) const

VectorType is not in the immediate context of get, 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:

template<typename T=VectorType, typename = std::enable_if_t<has_bracket_operator<T>::value> >
auto get(int i, rank<2>) const

And call get<>().

Barry
  • 286,269
  • 29
  • 621
  • 977
  • I'm not sure if the "immediate context" is what's relevant here (though the solution is sound). This is [CWG 1635](http://wg21.link/cwg1635). – T.C. Jun 15 '15 at 23:03
  • @T.C. Does that imply that it's correct to not compile but possibly shouldn't be, or that it should compile and the compilers are in error? – Barry Jun 16 '15 at 00:49
  • I have no idea what CWG is going to do about that issue. Right now I'd probably call this underspecified. – T.C. Jun 16 '15 at 00:51
4

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:

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=VectorType, typename = std::enable_if_t<has_bracket_operator<const V>::value> >
    auto get(int i, rank<2>) const
    {
        return v[i];
    }

    template<typename V=VectorType, typename = std::enable_if_t<has_parenthesis_operator<const V>::value> >
    auto get(int i, rank<1>) const
    {
        return v(i);
    }

    VectorType v;
};

This way, V is resolved when get is called, and it will correctly use SFINAE.

JKor
  • 3,822
  • 3
  • 28
  • 36
3

Or you can just use tag-dispatching:

auto get(int i) const
{
    return get(i, has_bracket_operator<VectorType>(), has_parenthesis_operator<VectorType>());
}

auto get(int i, std::true_type /*brackets*/, std::false_type /*parenthesis*/) const
{
    return v[i];
}

auto get(int i, std::false_type /*brackets*/, std::true_type /*parenthesis*/) const
{
    return v(i);
}

demo

grisha
  • 1,247
  • 1
  • 14
  • 20
  • 1
    I guess this will fail if the vector doesn't have `operator()(int)`. – davidhigh Jun 15 '15 at 22:00
  • why you think so ? http://coliru.stacked-crooked.com/a/0a32631cc21deeb3. I don't understand your remark. vector doesn't have `operator()(int)` – grisha Jun 15 '15 at 22:19
  • because I would have thought that the compiler instantiates those functions, regardless of the parameters passed to it. And when it tries to instantiate the `operator()` version, it fails. I guess here expression SFINAE is working: as `decltype(v(i))` is ill formed, the function is possibly disabled automatically (?). – davidhigh Jun 15 '15 at 22:36
  • 1
    @davidhigh Member functions of class templates are only instantiated when used. – Barry Jun 15 '15 at 22:40