在使用 CRTP 的 class 中检测成员函数

Detecting a member function in a class which uses CRTP

我正在尝试根据使用 CRTP 的子 class 中可用的功能自定义基础 classes 的实现。

我想要的基本想法:

// 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's solution (Compiler Explorer link):

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;
};

(Compiler Explorer link):

注意:实现与 return 类型不匹配。我还在 Library fundamentals 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>>;

Valentin Milea's solution (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);

你想要的在这种形式下显然是不可能的。必须在 class 完成之前知道 class 的父级,因此在知道 class 是否具有这样的成员函数之前。

你能做什么有点取决于 base 的不同实例化有多么不同。如果它们基本上是具有不同实现细节的相同接口,您可以编写另一个具有相同接口和变体成员的 class(std::variant 很遗憾是 C++17,但您可以使用动态多态性),所有调用都被转发到。然后在实例化的时候就可以决定使用哪个了。

你也可以在这个方向尝试一下:

#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;.

I've tried a variety of different implementations for has_inc_function<Child, void>, but all of them seem to fail on the case has_inc_and_crtp, and I can't figure out why.

问题(如果我理解正确的话)是,在 has_inc_and_crpt 的情况下,首先评估 has_inc_function 的值以确定第二个 Child 的默认值模板参数

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

也就是当Child(也就是has_inc_and_crpt)还不完整,所以取值iffalse,在下面使用

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

保持false.

How would I implement has_inc_function so that it works as I would expect in all these test case, or is what I want just not possible?

一个快速而肮脏的解决方案可以是向 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 时,

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

class是不同的,是在那一刻评估的,而has_inc_and_crpt是用inc()方法检测到的。

但这只能解决测试用例 (static_assert()) 级别的问题。

仍然存在问题(一个我不知道如何解决的问题),声明 base,默认值仍然为 false。所以(我想)has_inc_and_crpt 仍然 select 错误的 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, "");
 }