SFINAE and E1.E2
E1.E2
The other day on Slack someone asked whether there was anything that was easier to express using classic SFINAE techniques than using C++20 Concepts techniques. I suggested
template<class T, class U>
auto plus(T t, U u) > decltype(t + u)
{
return t + u;
}
This expresses the SFINAE constraint that this function shall not participate in
overload resolution unless t + u
is a wellformed expression. To do the same
with Concepts, idiomatically, you might say something like
template<class T, class U>
concept Plussable = requires(T t, U u) { t + u; };
template<class U, Plussable<U> T>
decltype(auto) plus(T t, U u)
{
return t + u;
}
And even then, I wondered, did the Concepts version really capture everything
about the original? The original expresses as part of its signature that its
return type is the type of t+u
; could this have a SFINAE effect?
I think the answer is “no,” in the case of t+u
. But consider:
template<class T>
auto dot1(T t) > decltype(t.u)
{
return t.u;
}
template<class T> requires requires(T t) { t.u; }
decltype(auto) dot2(T t)
{
return t.u;
}
void dot1(...);
void dot2(...);
struct A {
static int u[];
};
struct A10 {
static int u[10];
};
struct F {
static int u(int);
};
struct MF {
int u(int);
};
Godbolt yields the following results:
GCC  Clang  MSVC  ICC  

dot1(A()) 
dot1(...) 
dot1(...) 
dot1<A> > int[] 
dot1(...) 
dot2(A()) 
error  error  error  N/A 
dot1(A10()) 
dot1(...) 
dot1(...) 
dot1<A10> > int[10] 
dot1(...) 
dot2(A10()) 
error  error  error  N/A 
dot1(F()) 
dot1(...) 
dot1<F>() > int(&)(int) 
error  dot1(...) 
dot2(F()) 
error  dot2<F>() > int(&)(int) 
error  N/A 
dot1(MF()) 
dot1(...) 
dot1(...) 
error  dot1(...) 
dot2(MF()) 
dot2(...) 
error  error  N/A 
I agree with GCC’s behavior in all of these cases.

dot1<T>
should SFINAE away wheneverauto (T) > decltype(t.u)
is not a wellformed function type, and that includes wheneverdecltype(t.u)
is an array, function, or memberfunction type. 
Contrariwise,
dot2<T>
participates in overload resolution whenevert.u
is wellformed, and so it’s correct for each of its uses to give a hard (nonSFINAEfriendly) error.
The final case, dot2(MF())
, is different from the rest. Here, the expression t.u
itself is illformed per [expr.ref]/6.3.2:
[When
E2
refers to a nonstatic member function],E1.E2
is a prvalue. The expression can be used only as the lefthand operand of a member function call.
All four compilers agree that in normal code, even discarding the value counts as a “use”:
struct MF { int u(int); };
void f1() { MF mf; mf.u; } // error
void f2() { MF mf; (void)mf.u; } // error
However, every compiler has its own opinion about how this plays out in
a requires
expression. Godbolt:
template<class T> concept C1 =
requires (T t) { t.u; };
template<class T> concept C2 =
requires (T t) { (void)t.u; };
static_assert(C1<MF> && C2<MF>); // MSVC
static_assert(C1<MF> && !C2<MF>); // Clang
static_assert(!C1<MF> && !C2<MF>); // GCC
Again, my impression is that GCC is correct and the others are (at time of writing) wrong.