2

I am trying to customize a base classes' implementation based on the functions available in a child class using CRTP.

Basic idea of what I want:

// 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, "");
}

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. I tested with several different compilers via Compiler Explorer, and they all seem to give the same results.

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?

Implementations I've tried

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

TartanLlama's solution (Compiler Explorer link):

note: that is implementation doesn't match the return type. I've also included sample implementations of stuff in Library fundamentals TS v2 to make this work in 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 (I couldn't figure out how to get Boost to work with Compiler Explorer):

#include <boost/tti/has_member_function.hpp>

BOOST_TTI_TRAIT_HAS_MEMBER_FUNCTION(has_inc_function, inc);
Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
helloworld922
  • 10,801
  • 5
  • 48
  • 85
  • Sounds like you want "reflection". Which is a feature C++ does not have. – Jesper Juhl Feb 01 '20 at 16:36
  • @JesperJuhl: Reflection wouldn't help, since the derived class is incomplete at that point. There's nothing to reflect over. You have to use template metaprogramming cleverness instead. – Nicol Bolas Feb 01 '20 at 16:37

2 Answers2

2

What you want is in this form plainly not possible. The parent of a class has to be known before the class is complete, and hence before it is known whether the class has such a member function or not.

What you can do is a bit dependent on how different the different instantiations of base are. If they are basically the same interface with different implementation details, you can write another class that has the same interface and a variant member (std::variant is sadly C++17, but you could do the same with dynamic polymorphism) to which all calls are forwarded. Then the decision which to use can be done when instantiating.

You could also try something in this direction:

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

Here you only use the complete child of type in the definition, not in the declaration. To the point of the definition, the complete child is available, which it was not during declaration.

There are also other ways (I think, everything is not that easy) and what you can use really depends on your use-case, I would think.

PS: std::void_t is C++17, but it is only template<class...> using void_t = void;.

n314159
  • 4,990
  • 1
  • 5
  • 20
1

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.

The problem (if I understand correctly) is that, in the has_inc_and_crpt case, the value of has_inc_function is first evaluated to determine the default value for the Childs second template parameter

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

that is when Child (that is has_inc_and_crpt) is still incomplete, so the value if false, and in the following use

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

remain 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?

A quick and dirty solution could be add an additional dummy defaulted template parameter to has_inc_function.

By example

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

then use it in base explicating a special (different from the default) parameter

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

So, when you use has_inc_functin in the static assert,

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

the class is different, is evaluated in that moment and has_inc_and_crpt is detected with inc() method.

But this only resolve the problem at test case (static_assert()) level.

Still remain the problem (a problem that I don't how to solve) that, declaring base, the default value remain false. So (I suppose) has_inc_and_crpt still select the wrong base base.

The following is a full compiling example, following the jrok's solution.

#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, "");
 }
max66
  • 65,235
  • 10
  • 71
  • 111