c++c++20c++-conceptsc++-templates

How to check the signature of generic member function?


I have wrote a gerneric type Meta:

template <size_t N>
struct Meta
{
  // ...
};

And I need to make some MetaModifier(s) to build it in compile time.

MetaModifier(s) seem like below:

struct FooModifier
{
  template <size_t N, size_t N2>
  Meta<N2> apply(Meta<N> meta)
  {
    // ...
  }
};

Some of Modifiers' apply will change Meta<N> to Meta<N2>.

Use case:

constexpr Meta<1> default_meta{};

// Call variadic function
constexpr auto meta = apply_modifiers(default_meta, FooModifier{}, BarModifier{});

This is my implementation:

template <typename Modifier, size_t N>
static constexpr auto apply_modifier(Meta<N> meta, Modifier M)
{
  return M.apply(meta);
}

template <size_t N, typename FirstModifier, typename... RestModifiers>
static constexpr auto apply_modifiers(Meta<N> meta, FirstModifier F, RestModifiers... rest)
{
  return apply_modifiers(apply_modifier(meta, F), rest...);
}

template <size_t N>
static constexpr auto apply_modifiers(Meta<N> meta)
{
  return meta;
}

Currently it works, but I want to check types of Modifier(s) to make sure it has a method Meta<N2> apply(Meta<N>), report a friendly message if it is not matched. I tried to make a concept MetaModifier but failed. How can I write a correct concept or use some compile time checking to ensure it?

UPDATE: Is there a way to try two steps like below (pseudo code)?

template <typename Modifier, size_t N>
static constexpr auto apply_modifier(Meta<N> meta, Modifier M)
{
  // First step: test it has a method apply
  if constexpr (has_apply<Modifier, Meta<N>>)
  {
    // Second step: test the return type of apply
    auto r =  M.apply(meta);

    if constexpr (is_meta_type<decltype(r)>)
    {
      return r;
    }
    else
    {
      static_assert(false, "The return type of apply is not Meta<N>");
    }
  }
  else
  {
    static_assert(false, "Modifier does not have apply method");
  }
}

Solution

  • A concept is there to check if a type satisfies some property. It can't check if a template satisfies some property, because a template isn't a type.

    apply_modifier needs to know which N2 to use when it is passed a FooModifier, the easiest way is to make N2 a template argument of FooModifier.

    template <size_t N2>
    struct FooModifier
    {
      template <size_t N>
      Meta<N2> apply(Meta<N> meta)
      {
        // ...
      }
    };
    

    Another possibility is that N2 is dependant on N, e.g.

    struct BarModifier
    {
      template <size_t N>
      Meta<N + 1> apply(Meta<N> meta)
      {
        // ...
      }
    };
    

    Having done that, we can construct constraints for apply_modifier and apply_modifiers

    template <typename T>
    struct is_meta : std::false_type {};
    
    template <size_t N>
    struct is_meta<Meta<N>> : std::true_type {};
    
    template <typename T>
    concept meta = is_meta<T>::value;
    
    template <typename Mod, typename M>
    concept meta_modifier = meta<M> && requires(Mod mod, M m)
    {
      { mod.apply(m) } -> meta;
    }
    

    Our meta_modifier concept requires knowing the Meta that it will modify, which means that you can still check modifiers that only apply to certain Meta specialisations

    template <meta M, meta_modifier<M> Modifier>
    static constexpr auto apply_modifier(M meta, Modifier modifier)
    {
      return modifier.apply(meta);
    }
    
    template <meta M, meta_modifier<M> FirstModifier, typename... RestModifiers>
    static constexpr auto apply_modifiers(M meta, FirstModifier F, RestModifiers... rest)
    {
      return apply_modifiers(apply_modifier(meta, F), rest...);
    }
    
    template <meta M>
    static constexpr auto apply_modifiers(M meta)
    {
      return meta;
    }