danielspaniol
danielspaniol

Reputation: 2328

Conditional compilation inside macro

I want to bring something like type-classes to my project using templates. I also want to be able to generate standard-implementations with a macro, but only if a method is implemented in the class implementing the type-class.

To be more specific, I have a type-class Comp<T>, which has several methods. Assume it only has do_f. I want to be able to implement do_f manually, or call a DERIVE macro, that automatically calls T::f if T has a method f or do nothing if T has no f.

So the code I have looks like this:

template <typename T> struct Comp {
  static auto do_f(T&) -> void = delete;
};

#define HAS_METHOD(T, f) ... // works already

#define DERIVE(T)                                      /
  template<> struct Comp<T> {                          /
    static auto do_f(T &t) -> void {                      /
      /* Here I'd like to call T::f iff it exists */   /
      /* I am thinking about something like: */        /
      #if (HAS_METHOD(T, f))                           /
        t.f();                                         /
      #endif                                           /
    }                                                  /
  }; 

I could then use this macro like this:

struct Foo { auto f() -> void { } };
struct Bar { };
struct Baz { auto g() -> void { } };

// Generate Comp<Foo>, where do_f calls f
DERIVE(Foo)
// Generate Comp<Bar>, where do_f does nothing
DERIVE(Bar)
// Hand-implement Comp<Baz>
template<> struct Comp<Baz> {
  static auto do_f(Baz& b) -> void { b.g(); }
}

Based on the suggestion by Alessandro Teruzzi, I tried to use std::enable_if, but so far it only works in the generic Comp<T>. I would like to keep Comp<T> with only the = delete and have the conditional compilation in the DERIVE macro. If I move the enable_ifs into the macro, I get errors that enable_if cannot disable the functions.

Here is what it currently looks like

template<typename T> struct Comp {

  template<std::enable_if_t<HAS_METHOD(T, f), bool> = true>
  static auto do_f(T&) -> void {}

  template<std::enable_if_t<!HAS_METHOD(T, f), bool> = true>
  static auto do_f(T& t) -> void { t.f(); }
};

#define DERIVE(T) \
  template<> struct Comp<T> { /* set some other stuff */ }

Upvotes: 1

Views: 357

Answers (1)

danielspaniol
danielspaniol

Reputation: 2328

So, I worked around the conditional compilation in the macro by using a second template struct that can use enable_if. Whenever I tried to directly put enable_if in the template specialization created by the macro, the compiler would complain.

Advantages:

  • The main type-class templates stays clean
  • The macro stays simple
  • The approach seems flexible

Disadvantages:

  • Call chain has an extra step
  • It seems complicated
  • Duplication of the interface

If someone finds a nicer approach, I will accept their answer. Until then, here is what I did:

// The type-class
template <class T> struct Comp {
  static auto do_f(T&) -> void = delete;
};

// Helper for auto-deriving
template <class T> struct DerivedComp {
  template<std::enable_if_t<!HAS_METHOD(T,f), bool> = true>
  static auto do_f(T&) -> void {}
  
  template<std::enable_if_t<HAS_METHOD(T,f), bool> = true>
  static auto do_f(T& t) -> void { t.f(); }  
};

// Macro to automatically derive an implementation
#define DERIVE_COMP(T)                                            \
  template <> struct Comp<T> {                                    \
    static auto do_f(T& t) -> void { DerivedComp<T>::do_f(t); }   \
  };

Upvotes: 0

Related Questions