Здесь нет SFINAE. Первая перегрузка foo
выбрана потому, что она дает лучшее совпадение, чем другое (совпадение является точным, в отличие от другого). Вы можете попробовать здесь для получения дополнительной информации о том, что такое SFINAE, я бы также рекомендовал посмотреть этот потрясающий доклад , чтобы узнать, как это сделать правильно (доклад немного более продвинутый ).
Что означает SFINAE, так это то, что при выводе типов в шаблоне вы получаете плохо сформированный код, это не имеет значения, если что-то сопоставляется. В вашем случае оба шаблона совпадают, поэтому применяются обычные правила разрешения. С урезанными шаблонами ваш код сводится к:
struct Base { int value; }
struct Derived : public Base {};
int var;
void foo(Derived val) { var = val; }
void foo(const Base& val) { var = val.value; }
int main()
{
Derived bar;
bar.value = 3;
foo(bar); // will try to call first foo and compilation will fail
}