Обнаружение функции-члена в классе, который использует CRTP - PullRequest
2 голосов
/ 01 февраля 2020

Я пытаюсь настроить реализацию базовых классов на основе функций, доступных в дочернем классе, используя CRTP.

Основа c Идея того, что я хочу:

// has_inc_function<Child, void> should detect the presence of a member function void Child::inc()
template<class Child, bool = has_inc_function<Child, void>::value>
struct base
{
    // ... base implementation stuff
};

template<class Child>
struct base<Child, true>
{
    // ... base specialization implementation stuff
};

struct empty : public base<empty>
{};

struct has_inc
{
    void inc()
    {}
};

struct has_inc_and_crtp : public base<has_inc_and_crtp>
{
    void inc()
    {}
};

struct has_inc_and_misuse_crtp : public base<has_inc_and_misuse_crtp, true>
{
    void inc()
    {}
};

struct has_inc_and_misuse_crtp2 : public base<has_inc_and_misuse_crtp, false>
{
    void inc()
    {}
};

struct no_inc_and_misuse_crtp : public base<no_inc_and_misuse_crtp, true>
{
};

int main()
{
    static_assert(has_inc_function<empty, void>::value == false, "");
    static_assert(has_inc_function<has_inc, void>::value == true, "");
    static_assert(has_inc_function<has_inc_and_crtp, void>::value == true, "");
    static_assert(has_inc_function<has_inc_and_misuse_crtp, void>::value == true, "");
    static_assert(has_inc_function<has_inc_and_misuse_crtp2, void>::value == true, "");
    static_assert(has_inc_function<no_inc_and_misuse_crtp, void>::value == false, "");
}

Я пробовал множество различных реализаций для has_inc_function<Child, void>, но все они, похоже, не работают в случае has_inc_and_crtp, и я не могу понять, почему. Я протестировал несколько разных компиляторов через Compiler Explorer, и все они, похоже, дают одинаковые результаты.

Как мне реализовать has_inc_function, чтобы он работал так, как я ожидал во всех этих тестовых примерах, или что Я хочу просто не возможно?

Реализации, которые я пробовал

Решение jrok ( Ссылка в Compiler Explorer ):

template <class C, class Ret>
struct has_increment<C, Ret>
{
private:
  template <class T>
  static constexpr auto check(T*) -> typename std::is_same<
    decltype(std::declval<T>().inc()), Ret>::type;

  template <typename> static constexpr std::false_type check(...);

  typedef decltype(check<C>(nullptr)) type;

public:
  static constexpr bool value = type::value;
};

решение TartanLlama ( ссылка компилятора ):

примечание: то есть реализация не соответствует типу возвращаемого значения. Я также включил примеры реализации вещей в основы библиотеки TS v2, чтобы сделать эту работу в C ++ 14

struct nonesuch
{
  ~nonesuch() = delete;
  nonesuch(nonesuch const&) = delete;
  void operator=(nonesuch const&) = delete;
};

namespace detail {
template <class Default, class AlwaysVoid,
          template<class...> class Op, class... Args>
struct detector {
  using value_t = std::false_type;
  using type = Default;
};

template <class Default, template<class...> class Op, class... Args>
struct detector<Default, std::void_t<Op<Args...>>, Op, Args...> {
  using value_t = std::true_type;
  using type = Op<Args...>;
};

} // namespace detail

template <template<class...> class Op, class... Args>
using is_detected = typename detail::detector<nonesuch, void, Op, Args...>::value_t;

template <template<class...> class Op, class... Args>
using detected_t = typename detail::detector<nonesuch, void, Op, Args...>::type;

template <class Default, template<class...> class Op, class... Args>
using detected_or = detail::detector<Default, void, Op, Args...>;

template<class...> struct disjunction : std::false_type { };
template<class B1> struct disjunction<B1> : B1 { };
template<class B1, class... Bn>
struct disjunction<B1, Bn...> 
    : std::conditional_t<bool(B1::value), B1, disjunction<Bn...>>  { };

template <typename T>
using has_type_t = typename T::inc;

template <typename T>
using has_non_type_t = decltype(&T::inc);

template <typename T, class RetType>
using has_inc_function =
  disjunction<is_detected<has_type_t, T>, is_detected<has_non_type_t, T>>;

Решение Валентина Милеи ( Compiler Explorer Link ):

template <class C, class RetType>
class has_inc_function
{
    template <class T>
    static std::true_type testSignature(RetType (T::*)());

    template <class T>
    static decltype(testSignature(&T::inc)) test(std::nullptr_t);

    template <class T>
    static std::false_type test(...);

public:
    using type = decltype(test<C>(nullptr));
    static const bool value = type::value;
};

Boost TTI (я не мог понять, как заставить Boost работать с Compiler Explorer):

#include <boost/tti/has_member_function.hpp>

BOOST_TTI_TRAIT_HAS_MEMBER_FUNCTION(has_inc_function, inc);

Ответы [ 2 ]

2 голосов
/ 01 февраля 2020

То, что вы хотите, в этой форме явно невозможно. Родитель класса должен быть известен до его завершения и, следовательно, до того, как станет известно, имеет ли класс такую ​​функцию-член или нет.

То, что вы можете сделать, немного зависит от того, насколько различны различные экземпляры base. Если это в основном один и тот же интерфейс с разными деталями реализации, вы можете написать другой класс с таким же интерфейсом и вариантом члена (к сожалению, std::variant - это C ++ 17, но вы можете сделать то же самое с динамическим c полиморфизмом) на который переадресовываются все звонки. Тогда решение, которое использовать, может быть принято при создании экземпляра.

Вы также можете попробовать что-то в этом направлении:

#include <type_traits>
#include <iostream>

template<class Child>
struct base {
    int foo();
};

struct has_inc: base<has_inc> {
    void inc();
};

struct has_not_inc: base<has_not_inc> {
};

template<class Child, class = std::void_t<decltype(std::declval<Child>().inc())>>
struct mock {
    int foo(base<Child>*) { return 1;}
};

template<class Child>
struct mock<Child> {
    int foo(base<Child>*) { return 0;}
};

template<class Child>
int base<Child>::foo() {
    return mock<Child,void>().foo(this);
}

int main() {
    has_inc h;
    has_not_inc n;
    std::cout << h.foo() << " " << n.foo() << '\n';
}

Здесь вы используете только полный дочерний тип в определении, а не в декларации. Что касается определения, доступен полный потомок, которого не было при объявлении.

Есть и другие способы (я думаю, что не все так просто), и то, что вы можете использовать, действительно зависит от вашего вариант использования, я бы подумал.

PS: std::void_t - это C ++ 17, но это всего лишь template<class...> using void_t = void;.

1 голос
/ 01 февраля 2020

Я пробовал множество различных реализаций для has_inc_function<Child, void>, но все они, похоже, не работают в случае has_inc_and_crtp, и я не могу понять, почему.

Проблема (если я правильно понимаю) заключается в том, что в случае has_inc_and_crpt сначала оценивается значение has_inc_function, чтобы определить значение по умолчанию для второго параметра шаблона Child s

template<class Child, bool = has_inc_function<Child, void>::value>
struct base 

, то есть, когда Child (то есть has_inc_and_crpt) все еще не завершено, поэтому значение, если false, и при следующем использовании

static_assert(has_inc_function<has_inc_and_crtp, void>::value == true, "");

остаются false.

Как мне реализовать has_inc_function, чтобы он работал так, как я ожидал во всех этих тестовых случаях, или это то, что я хочу, просто невозможно?

Быстрое и грязное решение может добавьте дополнительный фиктивный параметр шаблона по умолчанию в has_inc_function.

В качестве примера

// ................................VVVVVVV  dummy and defaulted
template <typename C, typename RT, int = 0>
struct has_inc_function

, затем используйте его в base, объясняя специальный (отличный от значения по умолчанию) параметр

// ........................................................V  different from the default
template<class Child, bool = has_inc_function<Child, void, 1>::value>
struct base 

Итак, когда вы используете has_inc_functin в состоянии c assert,

static_assert(has_inc_function<has_inc_and_crtp, void>::value == true, "");

класса i s отличается, оценивается в тот момент, и has_inc_and_crpt обнаруживается с помощью метода inc().

Но это только решает проблему на уровне тестового набора (static_assert()).

Все еще остается проблема (проблема, которую я не знаю, как ее решить) в том, что, объявив base, значение по умолчанию остается ложным. Поэтому (я полагаю) has_inc_and_crpt все еще выбирает неправильную base базу.

Ниже приведен пример полной компиляции, следуя решению jrok.

#include <type_traits>

template <typename C, typename RT, int = 0>
struct has_inc_function
 {
   private:
      template <typename T>
      static constexpr auto check(T *) ->
      typename std::is_same<decltype(std::declval<T>().inc()), RT>::type;

      template <typename>
      static constexpr std::false_type check(...);

      using type = decltype(check<C>(nullptr));

   public:
      /// @brief True if there is an inc member function
      static constexpr bool value = type::value;
 };

template <typename Child, bool = has_inc_function<Child, void, 1>::value>
struct base 
 { };

template <typename Child>
struct base<Child, true>
 { };

struct empty : public base<empty>
 { };

struct has_inc
 { void inc() {} };

struct has_inc_and_crtp : public base<has_inc_and_crtp>
 { void inc() {} };

struct has_inc_and_misuse_crtp : public base<has_inc_and_misuse_crtp, true>
 { void inc() {} };

struct has_inc_and_misuse_crtp2 : public base<has_inc_and_misuse_crtp, false>
 { void inc() {} };

struct no_inc_and_misuse_crtp : public base<no_inc_and_misuse_crtp, true>
 { };

template <typename C, typename RT>
constexpr auto hif_v = has_inc_function<C, RT>::value;

int main ()
 {
   static_assert(hif_v<empty, void> == false, "");
   static_assert(hif_v<has_inc, void> == true, "");
   static_assert(hif_v<has_inc_and_crtp, void> == true, "");
   static_assert(hif_v<has_inc_and_misuse_crtp, void> == true, "");
   static_assert(hif_v<has_inc_and_misuse_crtp2, void> == true, "");
   static_assert(hif_v<no_inc_and_misuse_crtp, void> == false, "");
 }
...