我需要实现一种检测类型,例如is_invocable
,但是SFINAE似乎仅对替换失败进行了浅层检查,而对于我的is_invocable
,我需要能够优雅地检测是否该调用将完全编译。用C ++ 17可以实现吗?
#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起)]
那么有没有办法扩展/解决这个问题?
答案 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 {};
记录下限制,以警告程序员使用此特征。