为什么这个SFINAE片段不能用于g ++,而是在MSVC中工作?

Sta*_*ght 19 c++ templates sfinae c++11

在MSVC2017中,这工作正常,两个static_asserts都没有按预期触发:

template <typename T>
struct do_have_size {
    template <typename = decltype(std::declval<T>().size())>
    static std::true_type check(T);
    static std::false_type check(...);
    using type = decltype(check(std::declval<T>()));
};

int main() {
    using TR = typename do_have_size<std::vector<int>>::type;
    using FL = typename do_have_size<int>::type;

    static_assert(std::is_same<TR, std::true_type>::value, "TRUE");
    static_assert(std::is_same<FL, std::false_type>::value, "FALSE");
}
Run Code Online (Sandbox Code Playgroud)

但是,如果我在g ++ 7.1或clang 4.0中编译,我会得到以下编译器错误:

In instantiation of 'struct do_have_size<int>':
20:39:   required from here
9:24: error: request for member 'size' in 'declval<do_have_size<int>::TP>()', which is of non-class type 'int'
Run Code Online (Sandbox Code Playgroud)

根据我对SFINAE的理解,true_type返回函数的替换应该对int参数失败,并且应该选择下一个函数,就像在MSVC中一样.为什么clang和g ++根本没有编译它?

-std=c++17只用开关编译,可能需要更多东西?

vso*_*tco 23

SFINAE在这里不起作用,因为该类已经用T = intin 实例化了do_have_size<int>::type.SFINAE仅适用于模板函数候选列表,在您的情况下,您将在实例化中遇到硬错误

do_have_size<int>::type
Run Code Online (Sandbox Code Playgroud)

成员函数

template <typename = decltype(std::declval<int>().size())>
static std::true_type check(T);
Run Code Online (Sandbox Code Playgroud)

肯定是不合理的int.该

static std::false_type check(...);
Run Code Online (Sandbox Code Playgroud)

永远不会被考虑.所以gcc就在这里拒绝你的代码而MSVC2017不应该接受代码.

相关:std :: enable_if:参数与模板参数SFINAE在返回类型中工作但不作为模板参数

一种解决方案是使用void_t(因为C++ 17,但你可以在C++ 11/14中定义自己的),它将任何类型列表映射到void并启用疯狂的简单SFINAE技术,如此

#include <utility>
#include <vector>

template<typename...>
using void_t = void; // that's how void_t is defined in C++17

template <typename T, typename = void>
struct has_size : std::false_type {};

template <typename T>
struct has_size<T, void_t<decltype(std::declval<T>().size())>>
    : std::true_type {};

int main() {
    using TR = typename has_size<std::vector<int>>::type;
    using FL = typename has_size<int>::type;

    static_assert(std::is_same<TR, std::true_type>::value, "TRUE");
    static_assert(std::is_same<FL, std::false_type>::value, "FALSE");
}
Run Code Online (Sandbox Code Playgroud)

Live on Wandbox

是Walter Brown的Cppcon视频,它void_t非常详细地解释了这些技术,我强烈推荐它!


T.C*_*.C. 13

这与默认模板参数是否是函数模板签名的一部分完全无关.

真正的问题是它T是一个类模板参数,当您实例化类模板的定义时,实现可以立即将其替换decltype(std::declval<T>().size())为模板参数推导之外的默认模板参数,如果size不存在则会导致硬错误.

修复很简单; 只需使它依赖于函数模板的参数.

template <typename U, typename = decltype(std::declval<U>().size())>
static std::true_type check(U);
Run Code Online (Sandbox Code Playgroud)

(您的实现还有其他问题,例如它需要一个可移动构造的非抽象T,并且不需要size()是可循环的,但它们不是您所看到的错误的原因.)


Yak*_*ont 5

@vsoftco回答"gcc拒绝你的代码是对的".我同意.

要解决,我说这样做:

namespace details {
  template<template<class...>class Z, class, class...Ts>
  struct can_apply:std::false_type{};
  template<class...>struct voider{using type=void;};
  template<class...Ts>using void_t = typename voider<Ts...>::type;

  template<template<class...>class Z, class...Ts>
  struct can_apply<Z, void_t<Z<Ts...>>, Ts...>:std::true_type{};
}
template<template<class...>class Z, class...Ts>
using can_apply=details::can_apply<Z,void,Ts...>;
Run Code Online (Sandbox Code Playgroud)

这是一个can_apply使这种SFINAE简单的库.

现在编写其中一个特征就像这样简单:

template<class T>
using dot_size_r = decltype( std::declval<T>().size() );

template<class T>
using has_dot_size = can_apply< dot_size_r, T >;
Run Code Online (Sandbox Code Playgroud)

测试代码:

int main() {
  static_assert( has_dot_size<std::vector<int>>{}, "TRUE" );
  static_assert( !has_dot_size<int>{}, "FALSE" );
}
Run Code Online (Sandbox Code Playgroud)

实例.

在C++ 17中,您可以移动到更少的declval填充表达式.

#define RETURNS(...) \
  noexcept(noexcept(__VA_ARGS__)) \
  -> decltype(__VA_ARGS__) \
  { return __VA_ARGS__; }

template<class F>
constexpr auto can_invoke(F&&) {
  return [](auto&&...args) {
    return std::is_invocable< F(decltype(args)...) >{};
  };
}
Run Code Online (Sandbox Code Playgroud)

can_invoke接受一个函数f并返回一个"invokation tester".invokation测试程序接受参数,然后返回,true_type如果这些参数有效传递给f,false_type否则.

RETURNS使单一语句lambda SFINAE易于使用变得容易.在C++ 17中,如果可能的话,lambda的操作是constexpr(这就是我们在这里需要C++ 17的原因).

然后,这给了我们:

template<class T>
constexpr auto can_dot_size(T&& t) {
  return can_invoke([](auto&& x) RETURNS(x.size()))(t);
}
Run Code Online (Sandbox Code Playgroud)

现在,我们经常这样做,因为我们想要尽可能调用.size(),否则返回0.

template<class T, class A, class...Bs>
decltype(auto) call_first_valid(T&& t, A&& a, Bs&&...bs) {
  if constexpr( can_invoke(std::forward<A>(a))(std::forward<T>(t)) ) {
    return std::forward<A>(a)(std::forward<T>(t));
  else
    return call_first_valid(std::forward<T>(t), std::forward<Bs>(bs)...);
}
Run Code Online (Sandbox Code Playgroud)

现在我们可以

template<class T>
std::size_t size_at_least( T&& t ) {
  return call_first_valid( std::forward<T>(t),
    [](auto&& t) RETURNS(t.size()),
    [](auto&&)->std::size_t { return 0; }
  );
}
Run Code Online (Sandbox Code Playgroud)

因为它发生,@Barry提出在C++ 20的特征,它取代[](auto&& f) RETURNS(f.size())[](auto&& f)=>f.size()(和更多).