当替换在代码中进一步导致一些编译错误时,如何执行“深度” SFINAE?

时间:2019-07-01 20:41:42

标签: c++ sfinae

我需要实现一种检测类型,例如is_invocable,但是SFINAE似乎仅对替换失败进行了浅层检查,而对于我的is_invocable,我需要能够优雅地检测是否该调用将完全编译。用C ++ 17可以实现吗?

https://godbolt.org/z/Y72Dov

#include <type_traits>

struct supported_by_f1_and_f2 {};
struct not_supported_by_f1 {};
struct not_supported_by_f2 {};

template<typename T>
auto f2(T t, std::enable_if_t<!std::is_same_v<T, not_supported_by_f2>>* = 0) {}

template<typename T>
auto f1(T t, std::enable_if_t<!std::is_same_v<T, not_supported_by_f1>>* = 0) {
    return f2(t);
}

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

template <typename T>
struct is_f1_invocable<T, std::void_t<decltype(f1(std::declval<T>()))>> : public std::true_type {};

using supported_by_f1_and_f2_ok_t = is_f1_invocable<supported_by_f1_and_f2>;
using not_supported_by_f1_ok_t = is_f1_invocable<not_supported_by_f1>;
using not_supported_by_f2_ok_t = is_f1_invocable<not_supported_by_f2>;

supported_by_f1_and_f2_ok_t supported_by_f1_and_f2_ok;
not_supported_by_f1_ok_t not_supported_by_f1_ok;

// Why substitution failure, that occures during 'return f2(t);', is not detected here during the instantiation of 'is_f1_invocable'?
not_supported_by_f2_ok_t not_supported_by_f2_ok; // error: no matching function for call to 'f2'

编辑:

来自https://en.cppreference.com/w/cpp/language/sfinae

  

仅函数类型或其模板参数类型[或其显式说明符(自C ++ 20起)]的直接上下文中的类型和表达式失败是SFINAE错误。如果对替换类型/表达式的求值引起副作用,例如某些模板专门化的实例化,隐式定义的成员函数的生成等,则将这些副作用中的错误视为硬错误。 [lambda表达式不被视为直接上下文的一部分。 (自C ++ 20起)]

那么有没有办法扩展/解决这个问题?

2 个答案:

答案 0 :(得分:2)

您要寻找的概念是使f1对SFINAE友好。这要求f1 author 采取一些措施,以确保 user 具有某种方法来检测对f1的呼叫会生病格式,导致出现软错误。如果未将f1写为对SFINAE友好,则没有解决方法。

要使f1对SFINAE友好,我们需要确保在实例化f1 body 时发生一些编译错误之前,首先要满足以下条件:会导致该错误使f1 signature 无效,因此,当封闭的实例试图调用或获取f1的地址时,SFINAE会踢去删除{{1} },因为在实例化f1的签名的即时上下文中遇到了错误。

换句话说,在这种情况下,由于我们认为在f1主体中调用f2(t)的实例化可能会导致错误,因此我们应该在{{ 1}}。例如,我们可以这样做:

f1

因此,现在,f1的实例化开始了template <typename T> auto f1(T t, std::enable_if_t<...>* = 0) -> decltype(f2(t)) { // actually you may want to decay the type but w/e return f2(t); } 的替代和扣除过程,而这又开始了f1(std::declval<T>())的替代和扣除过程。此时,由于f1,在f2实例化的直接上下文中的enable_if签名中发生了替换失败,因此删除了{{1} }重载集合中的模板。结果,必须从一个空的重载集中解决对f2签名中对f2的调用,这意味着重载解析失败是在f2实例化的直接上下文中进行的。最后,这也从重载集中删除了f2模板,这又由于空载重载而导致重载解析失败,这一次是在f1实例化的直接上下文中,这就是我们想要。

类似地,如果在实例化f1的主体时可能出了什么问题,则我们需要修改f1的签名以解决该可能性,并确保SFINAE以类似的方式传播。

当然,您必须决定要走多远。在某个时候,您可能会决定确实要导致硬错误,而不是简单地从重载集中删除签名,而将软错误传播到封闭的实例中。

答案 1 :(得分:1)

不,这是不可能的,正是由于[temp.fct.spec]/8中的“即时上下文”规则,并且由您的cppreference.com链接所描述。

当然,如果f1在其not_supported_by_f2检查中检查了enable_if_t,或者直接检查了f2(t)是否可调用,那么它将是“更多SFINAE-正确”,这不是问题。但是,如果您不能更改f1的声明,那么您只能做的是:

  • 为特征添加额外的检查,以解决特定的已知故障(尽管f1位于不受您控制的库中,并且其实现在更高版本中发生了变化...)

    template <typename T>
    struct is_f1_invocable<T,
        std::void_t<decltype(f1(std::declval<T>())),
                    decltype(f2(std::declval<T>()))>> // hack
      : public std::true_type {};
    
  • 记录下限制,以警告程序员使用此特征。