1

In this code

struct A {int commmon; int rare;};
struct B {int commmon;};

struct L {
    template<class T>
    int f(const T& t) {return t.commmon;}
    template<class T>
    int f(T& t) {return t.rare;}
};

void func() {
    A a; B b; L l;

    l.f(a);
    l.f(B{});
    l.f(b);
}

the final lines gives me the error

In instantiation of ‘int L::f(T&) [with T = B]’:
error: ‘struct B’ has no member named ‘rare’

But according to my understanding of SFINAE, the second overload should be ignored because of the substitution failure in the body. Why doesn't this happen?

EDIT: If I change the return type of the second overload to decltype(T::rare), it does what I want. So where does my SF need to happen to be NAE?

Dan
  • 12,409
  • 3
  • 50
  • 87
  • 5
    read the explanation here: https://en.cppreference.com/w/cpp/language/sfinae In short: The SF has to be in the function declaration, not in the body. – mch Jul 17 '18 at 07:49
  • 1
    @mch Wow, that explanation is much better than Wikipedia's. – Dan Jul 17 '18 at 07:51
  • 1
    You might use something like `template auto f(T& t) -> decltype(t.rare) {return t.rare;}` to enable SFINAE for your function. – Jarod42 Jul 17 '18 at 07:58

3 Answers3

2

SFINAE does not apply to function bodies [temp.deduct/8]:

Only invalid types and expressions in the immediate context of the function type, its template parameter types, and its explicit-specifier can result in a deduction failure.

Daniel Langr
  • 22,196
  • 3
  • 50
  • 93
2

The least verbose way to fix that is to use an auto return type with a trailing return type on the more constraint overload:

struct L {
    template <class T>
        auto f(const T& t) {return t.commmon;}
    template <class T>
       auto f(T& t) -> decltype(t.rare) {return t.rare;}
};

The advantage of this approach is that the constraint is specified at a point where the compiler has already seen the function argument, allowing for a shorter notation than std::enable_if clauses in the declaration of template parameters:

#include <type_traits>

struct L {
   template <class T>
       int f(const T& t) {return t.commmon;}
   template <class T, std::enable_if_t<std::is_same_v<std::decay_t<T>, A>, int> = 0>
       int f(T& t) { return t.rare;}
};

Note further that the more constrained function won't be called when passing an rvalue argument. You might want to fix that by changing the function signatures to

template<class T /*, ... */>
    int f(T&& t) { /* ... */ }
lubgr
  • 37,368
  • 3
  • 66
  • 117
0

Template arguments are deduced before the function actually is instantiated. At the time the implementation is evaluated, the deduction has already occurred. So all that's relevant for deduction is the function signature, and from the existing ones, the non-const variant is fine to select.

If you want to apply get SFINAE applied depending on members existing, you have to do this within the function signature already. lubgr's answer reflects this nicely: The return type is decltype(t.rare); if T does not provide a rare member, the return type cannot be deduced and thus the overload is not considered during resolution.

Found two other answers dealing with the matter you might be interested in: C++11, compatible to pre-C++11

Aconcagua
  • 24,880
  • 4
  • 34
  • 59