Почему эти шаблонные функции не могут принимать аргументы?

Я пытаюсь использовать пару templatized функций для Ошибка замещения не является ошибкой (SFINAE). И я могу сделать это вот так:

template<typename R, typename S = decltype(declval<R>().test())> static true_type Test(R*);
template<typename R> static false_type Test(...);

Но я не понимаю, как аргумент делает этот SNFIAE. Похоже, я должен просто удалить аргументы, и выбор шаблона будет работать точно так же:

template<typename R, typename S = decltype(declval<R>().test())> static true_type Test();
template<typename R> static false_type Test();

Но это не так, я получаю:

Вызов перегруженного 'Test()' неоднозначен

Что происходит с этими аргументами, которые делают эту работу SFINAE?

Ответ 1

Второй пример не скомпилирован, так как есть две перегрузки Test с одинаковой сигнатурой, поскольку аргументы типа шаблона по умолчанию не являются частью сигнатуры функции. Это не разрешено.

Ваш первый пример работает следующим образом:

Когда тип R имеет в нем функцию Test, оба Test становятся действительными кандидатами перегрузки. Однако функции эллипсиса имеют более низкий ранг, чем неэллиптические, и, таким образом, компилятор выбирает первую перегрузку, возвращая true_type.

Когда R не имеет на нем Test, первая перегрузка исключается из набора разрешений перегрузки (SFINAE при работе). Осталось только второе, которое возвращает false_type.

Ответ 2

Вопрос был дан ответ, но, возможно, полезно углубиться в более глубокое объяснение.

Надеюсь, эта аннотированная программа сделает все более ясным:

#include <utility>
#include <iostream>

// define the template function Test<R> if and only if the expression
// std::declval<R>().test()
// is a valid expression.
// in which case Test<R, decltype(std::declval<R>().test())>(0) will be preferrable to... (see after)
template<typename R, typename S = decltype(std::declval<R>().test())> 
  static std::true_type Test(R*);

// ...the template function Test<R>(...)
// because any function overload with specific arguments is preferred to this
template<typename R> static std::false_type Test(...);


struct foo
{
  void test();
};

struct bar
{
  // no test() method
//  void test();
};


// has_test<T> deduces the type that would have been returned from Test<T ... with possibly defaulted args here>(0)
// The actual Test<T>(0) will be the best candidate available
// For foo, it Test<foo, decltype(std::declval<R>().test())>(foo*)
// which deduces to
// Test<foo, void>(foo*)
// because that a better match than Test<foo>(...)
//
// for bar it Test<bar>(...)
// because Test<bar, /*error deducing type*/>(bar*)
// is discarded as a candidate, due to SFNAE
//
template<class T>
constexpr bool has_test = decltype(Test<T>(0))::value;


int main()
{
  std::cout << has_test<foo> << std::endl;
  std::cout << has_test<bar> << std::endl;
}