Тестирование, если функция-член существует с использованием переменных

Итак, я очень хорошо знаком с парадигмой тестирования, если существует функция-член. В настоящее время этот код работает:

#include <iostream>
#include <type_traits>

struct has_mem_func_foo_impl {
    template <typename U, U>
    struct chk { };

    template <typename Class, typename Arg>
    static std::true_type has_foo(chk<void(Class::*)(Arg), &Class::foo>*);

    template <typename, typename>
    static std::false_type has_foo(...);
};

template <typename Class, typename Arg>
struct has_mem_func_foo : decltype(has_mem_func_foo_impl::template has_foo<Class,Arg>(nullptr)) { };


struct bar {
    void foo(int) { }
};

int main() {
    static_assert( has_mem_func_foo<bar, int>::value, "bar has foo(int)" );
}

К сожалению, если я сделаю небольшую корректировку:

#include <iostream>
#include <type_traits>

struct has_mem_func_foo_impl {
    template <typename U, U>
    struct chk { };

    template <typename Class, typename... Arg>
    static std::true_type has_foo(chk<void(Class::*)(Arg...), &Class::foo>*);

    template <typename, typename...>
    static std::false_type has_foo(...);
};

template <typename Class, typename... Arg>
struct has_mem_func_foo : decltype(has_mem_func_foo_impl::template has_foo<Class,Arg...>(nullptr)) { };


struct bar {
    void foo(int) { }
};

int main() {
    static_assert( has_mem_func_foo<bar, int>::value, "bar has foo(int)" );
}

мое статическое утверждение терпит неудачу. У меня создалось впечатление, что вариативные паттерны параметров шаблонов обрабатываются одинаково, когда они расширяются в свои места. Как gcc, так и clang производят неудачное статическое утверждение.

Таким образом, реальный корень моего вопроса - это стандартное поведение? Он также терпит неудачу при тестировании на наличие вариационной функции шаблона-члена.

Ответы

Ответ 1

Проблема, которую я вижу, заключается в том, что Arg... передается int недостаточно. Для компилятора было бы полезно добавить новые аргументы в конец.

Сокращение того, что добавить к концу его из nullptr_t, невозможно, поэтому компилятор говорит: "Я сдаюсь, а не этот случай".

Но нам не нужно иметь Arg... в разрешимом контексте для вашего трюка для работы:

#include <iostream>
#include <type_traits>

template<class Sig>
struct has_mem_func_foo_impl;

template<class R, class...Args>
struct has_mem_func_foo_impl<R(Args...)> {
  template <typename U, U>
  struct chk { };

  template <typename Class>
  static constexpr std::true_type has_foo(chk<R(Class::*)(Args...), &Class::foo>*) { return {}; }

  template <typename>
  static constexpr std::false_type has_foo(...) { return {}; }
};

template <typename Class, typename Sig>
struct has_mem_func_foo :
  decltype(has_mem_func_foo_impl<Sig>::template has_foo<Class>(nullptr))
{};

struct bar {
  void foo(int) { }
};


int main() {
  static_assert( has_mem_func_foo<bar, void(int)>::value, "bar has foo(int)" );
}

мы перемещаем Args... в сам класс, а затем передаем этот тип только функции. Это блокирует вычет, что делает преобразование nullptr в указатель функции-члена выполнимым, и все работает снова.

Я также включил некоторый улучшенный синтаксис на основе подписи, что также означает, что он поддерживает соответствие типа возвращаемого значения.

Обратите внимание, что вы можете задавать неправильный вопрос. Вы спрашиваете, есть ли функция-член с определенной сигнатурой: часто то, что вы хотите знать, - это если есть функция-член, которая invokable с определенным набором аргументов, с типом возврата, совместимым с вашим возвращаемым значением.

namespace details {
  template<class T, class Sig, class=void>
  struct has_foo:std::false_type{};

  template<class T, class R, class... Args>
  struct has_foo<T, R(Args...),
    typename std::enable_if<
      std::is_convertible<
        decltype(std::declval<T>().foo(std::declval<Args>()...)),
        R
      >::value
      || std::is_same<R, void>::value // all return types are compatible with void
      // and, due to SFINAE, we can invoke T.foo(Args...) (otherwise previous clause fails)
    >::type
  >:std::true_type{};
}
template<class T, class Sig>
using has_foo = std::integral_constant<bool, details::has_foo<T, Sig>::value>;

который пытается вызвать T.foo(int) и проверяет совместимость возвращаемого значения.

Для удовольствия я сделал тип has_foo фактически true_type или false_type, а не унаследован. Я мог бы просто:

template<class T, class Sig>
using has_foo = details::has_foo<T, Sig>;

если мне не нужна эта дополнительная функция.