神奇的VS

环境一: Windows 10 + Visual Stuido 2017 社区版 + C++标准最新草案 (笑话,对C++17的支持少的可怜,is_same_v都有了却不支持inline variable。。。)
环境二: FreeBSD 11 + Clang 4.0 + -std=c++17
环境三: openSUSE Tumbleweed + Clang 4.0 (-std=c++1y) + GCC 7.0.1 (-std=c++17)

vs_ok

gcc_clang_ok

如你所见,这一次VS又傻逼了,下面是“正常的”代码

#include <type_traits>

template<typename F, typename... Args>
struct have_same_args
{
  private:
    template<typename Func>
    static std::false_type test(...);
    template<typename Func, typename = decltype(std::declval<F>()(std::declval<Args>()...))>
    static std::true_type test(int);
  public:
    constexpr static bool value = decltype(test<F>(0))::value;
};

template<typename> struct check;

template<typename Res, typename... Args>
struct check<Res(Args...)>
{
  private:
    using Sig = Res(Args...);
  public:
    template<typename R, typename... FArgs>
    constexpr static bool is_same(R, FArgs...)
    {
      return std::is_same<Res, R>::value && have_same_args<Sig, FArgs...>::value;
    }
};

int main()
{
  static_assert(check<int(int, const char*)>::is_same(1, 1, 2.33), "not same");
}

一个是模板函数,一个是非模板的C风格的变参函数,显示调用模板函数,根据匹配规则,vs调用模板函数test,但是似乎忽略了默认模板参数的推导,而GCC和Clang在推导默认模板参数的时候发现函数签名不一致,于是报错退出了,并不是因为static_assert失败,而上面的代码才会导致static_assert失败,并且同样会出现函数签名不一致的报错。


无论如何,上面的代码都不是最终想要的,这个才是!
对于类成员函数相当于做了remove_cv,对于λ函数,相当于是匿名类+operator()而已,和成员函数没差多少

#include <functional>
#include <type_traits>
#include <tuple>

template<typename, typename> struct is_match;

template<typename F, typename Res, typename... Args>
struct is_match<F, Res(Args...)>
{
  private:
    template<typename Func> struct extract;

    template<typename R, typename... A>
    struct extract<R(A...)>
    {
      using ires = R;
      using iargs = std::tuple<A...>;
    };

    template<typename R, typename... A>
    struct extract<R(*)(A...)> : public extract<R(A...)> {};

    template<typename R, typename... A>
    struct extract<std::function<R(A...)>> : public extract<R(A...)> {};

    template<typename R, typename Class, typename... A>
    struct extract<R(Class::*)(A...)> : public extract<R(A...)> {};

    template<typename R, typename Class, typename... A>
    struct extract<R(Class::*)(A...) const> : public extract<R(A...)> {};

    template<typename R, typename Class, typename... A>
    struct extract<R(Class::*)(A...) volatile> : public extract<R(A...)> {};

    template<typename R, typename Class, typename... A>
    struct extract<R(Class::*)(A...) const volatile> : public extract<R(A...)> {};

    // immutable lambda call <R(::*)(A..) const>
    template<typename lambda>
    struct extract : public extract<decltype(&lambda::operator())> {};

    using res = Res;
    using args = std::tuple<Args...>;
    using tmp = extract<F>;
    using ires = typename tmp::ires;
    using iargs = typename tmp::iargs;
  public:
    constexpr static bool ret_same = std::is_same<res, ires>::value;
    constexpr static bool args_same = std::is_same<args, iargs>::value;
    constexpr static bool same = ret_same && args_same;
};

int foo(bool, const char*) { return 0; }

class Foo
{
  public:
    int foo(int, const char*) { return 0; }
    int operator()(int, const char*) { return 0; }
    static int bar(int, const char*) { return 0; }
};

int main()
{
  using type = int(int, const char*);
  auto bar = [](int, const char*) -> int { return 0; };
  static_assert(is_match<decltype(bar), type>::same, "not same");
  static_assert(is_match<decltype(&Foo::foo), type>::same, "not same");
  static_assert(is_match<decltype(&Foo::operator()), type>::same, "not same");
  static_assert(is_match<decltype(&Foo::bar), type>::same, "not same");
  static_assert(is_match<std::function<type>, type>::same, "not same");
  static_assert(is_match<decltype(foo), type>::same,
      "***return type or arguments type not same***");
}