#include <type_traits>
#include <vrm/pp.hpp>
#include <iostream>
#include <tuple>
#include <utility>
#include <experimental/tuple>

template <typename T>
struct type_w
{
    using type = T;
};

template <typename T>
constexpr type_w<T> type_c{};

template <typename TF>
struct validity_checker
{
    template <typename... Ts>
    constexpr auto operator()(Ts... ts)
    {
        return std::is_callable<std::decay_t<TF>(
            typename decltype(ts)::type...)>{};
    }
};

template <typename T, typename TF>
constexpr auto operator|(T x, validity_checker<TF> vc)
{
    return std::apply(vc, x);
}

template <typename TF>
constexpr auto is_valid(TF)
{
    return validity_checker<TF>{};
}

// The other `IS_VALID_...` should be generated by a script (or by an higher
// order preprocessor macro).

#define IS_VALID_2_EXPANDER(type0, type1) \
    (type_c<type0>, type_c<type1>)

#define IS_VALID_1_EXPANDER(type0) \
    (type_c<type0>)

#define IS_VALID_2(...) \
    is_valid([](auto _0, auto _1) constexpr->decltype(__VA_ARGS__){}) \
        IS_VALID_2_EXPANDER

#define IS_VALID_1(...) \
    is_valid([](auto _0) constexpr->decltype(__VA_ARGS__){}) \
        IS_VALID_1_EXPANDER

#define IS_VALID(...) \
    VRM_PP_CAT(IS_VALID_, VRM_PP_ARGCOUNT(__VA_ARGS__))(__VA_ARGS__)

struct Cat
{
    Cat() = delete;
    void meow() const
    {
        std::cout << "meow\n";
    }
};

struct Dog
{
    Dog() = delete;
    void bark() const
    {
        std::cout << "bark\n";
    }
};

template <typename T>
auto make_noise(const T& x)
{
    if constexpr(IS_VALID(_0.meow())(T))
    {
        x.meow();
    }
    else if constexpr(IS_VALID(_0.bark())(T))
    {
        x.bark();
    }
    else
    {
        struct cannot_meow_or_bark;
        cannot_meow_or_bark{};
    }
}

int main()
{
    make_noise(Cat{});
    make_noise(Dog{});
    // make_noise(int{});
}
