Search code examples
c++type-traitsc++-concepts

Detect existance of function using `concepts` and not `type_traits`


In a c++ program decisions are made compile time depending on if a specific overload of the function func exists.

Examples of func overload signatures are

void func(M<A>);
void func(M<B>, C);
void func(M<D>, E, F);
...

where M and A - F are user defined types.

This is solved with the use of type_traits and specifically std::void_t as illustrated in the following self contained program.

#include <type_traits>
#include <iostream>

// Dummy structs
struct A{};
struct B{};
struct C{};
struct D{};
struct E{};
struct F{};

template<typename I>
struct M{};

namespace fun {
// Functions we lik to detect compile time
  void func(M<A>){}
  void func(M<B>, C){}
  void func(M<D>, E, F){}
}

// Compile time check if 'func' with specific arguments exists.
//
namespace find_function {
namespace internal {

// Template variable func that defaults to false.
//
template <typename, typename = void, typename...>
constexpr bool func = false;

// Specialization of template variable func.
// The template argument std::void_t<decltype(transform::func(std::declval<Arg1>(), std::declval<SomeState>()...))>
// has the type 'void' if the decltype expression is valid after substitution.
//
template <typename Arg1, typename... SomeState>
constexpr bool func<Arg1,
                    std::void_t<decltype(fun::func(std::declval<Arg1>(), std::declval<SomeState>()...))>,
                    SomeState...> = true;
} // namespace internal

// Interface variable to internal::func.
// template arguments, Arg1, void, SomeState..., will match the specialization above if the decltype expression can be evaluated and
// find_function::func will evaluate to true. Else find_function::func will be false.
//
template <typename Arg1, typename... SomeState>
constexpr bool func = internal::func<Arg1, void, SomeState...>;

} // namespace find_function

int main() {
  if constexpr (find_function::func<M<A>> && find_function::func<M<B>, C> && find_function::func<M<D>, E, F>) {
    std::cout << "Exists" << std::endl;
  }
  if constexpr (!find_function::func<M<B>, B, C>) {
    std::cout << "Does not exist" << std::endl;
  }
}

Note that main contains a rather ridiculous use of the functionality. In a real situation the template arguments of find_function::func would be deduced in recursive template context.

My question is if there is a way of writing this code using concepts and not std::void_t? I'm not looking for a solution using enable_if that I find even more difficult to read compared to the presented solution.


Solution

  • As Jarod42 said (and adjusting for the slightly changed question), you can just do it like this:

    template <class... Ts>
    concept func = requires(Ts ... vs)
    {
        fun::func(vs...);
    };
    

    demo