如何执行 "deep" SFINAE,即当替换在代码中进一步导致一些编译错误时?
How to do a "deep" SFINAE, i.e., when the substitution causes some compilation errors further in the code?
我需要实现一个检测类型,比如 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 :
Only the failures in the types and expressions in the immediate context of the function type or its template parameter types [or its explicit specifier (since C++20)] are SFINAE errors. If the evaluation of a substituted type/expression causes a side-effect such as instantiation of some template specialization, generation of an implicitly-defined member function, etc, errors in those side-effects are treated as hard errors. [A lambda expression is not considered part of the immediate context. (since C++20)]
那么有办法extend/workaround吗?
不,这是不可能的,正是因为 [temp.fct.spec]/8 中的 "immediate context" 规则以及您的 cppreference.com link.
所描述的
当然,如果 f1
在其 enable_if_t
检查中直接检查 not_supported_by_f2
,或者直接检查 f2(t)
是否可调用,那么它将是 "more SFINAE-correct",这不是问题。但是如果你不能改变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 {};
记录限制,以警告使用此特性的程序员。
您正在寻找的概念是 f1
SFINAE 友好。这需要 f1
的 author 采取一些措施来确保 user 有某种方法检测对 f1
将是 ill-formed,导致软错误。如果 f1
没有写成对 SFINAE 友好,则没有解决方法。
为了使 f1
SFINAE 友好,我们需要确保在实例化 f1
的 body 时出现一些编译错误之前,首先,导致该错误的条件会导致 f1
的 signature 无效,因此当封闭实例化尝试调用或获取 f1
的地址时,SFINAE开始从重载集中删除 f1
,因为在实例化 f1
的签名的直接上下文中遇到错误。
换句话说,在这种情况下,由于我们认为 f1
主体中调用 f2(t)
的实例化可能会导致错误,因此我们应该在签名中复制该调用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
,f2
的签名中发生了替换失败,它位于 f2
实例化的直接上下文中,因此删除了 f2
来自重载集的模板。因此,必须从空重载集解析 f1
签名中对 f2
的调用,这意味着重载解析失败发生在 f1
实例化的直接上下文中。最后,这也从重载集中删除了 f1
模板,再次导致由于重载集为空而导致重载解析失败,这次是在 is_f1_invocable
实例化的直接上下文中,这就是我们想要。
类似地,如果在实例化 f2
的主体时出现问题,那么我们需要修改 f2
的签名以解决这种可能性,并确保 SFINAE 在类似的环境中传播时尚
当然,您必须决定要走多远。在某些时候,您可能会决定此时确实要引起硬错误,而不是简单地从重载集中删除签名,将软错误传播到封闭的实例化中。
我需要实现一个检测类型,比如 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 :
Only the failures in the types and expressions in the immediate context of the function type or its template parameter types [or its explicit specifier (since C++20)] are SFINAE errors. If the evaluation of a substituted type/expression causes a side-effect such as instantiation of some template specialization, generation of an implicitly-defined member function, etc, errors in those side-effects are treated as hard errors. [A lambda expression is not considered part of the immediate context. (since C++20)]
那么有办法extend/workaround吗?
不,这是不可能的,正是因为 [temp.fct.spec]/8 中的 "immediate context" 规则以及您的 cppreference.com link.
所描述的当然,如果 f1
在其 enable_if_t
检查中直接检查 not_supported_by_f2
,或者直接检查 f2(t)
是否可调用,那么它将是 "more SFINAE-correct",这不是问题。但是如果你不能改变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 {};
记录限制,以警告使用此特性的程序员。
您正在寻找的概念是 f1
SFINAE 友好。这需要 f1
的 author 采取一些措施来确保 user 有某种方法检测对 f1
将是 ill-formed,导致软错误。如果 f1
没有写成对 SFINAE 友好,则没有解决方法。
为了使 f1
SFINAE 友好,我们需要确保在实例化 f1
的 body 时出现一些编译错误之前,首先,导致该错误的条件会导致 f1
的 signature 无效,因此当封闭实例化尝试调用或获取 f1
的地址时,SFINAE开始从重载集中删除 f1
,因为在实例化 f1
的签名的直接上下文中遇到错误。
换句话说,在这种情况下,由于我们认为 f1
主体中调用 f2(t)
的实例化可能会导致错误,因此我们应该在签名中复制该调用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
,f2
的签名中发生了替换失败,它位于 f2
实例化的直接上下文中,因此删除了 f2
来自重载集的模板。因此,必须从空重载集解析 f1
签名中对 f2
的调用,这意味着重载解析失败发生在 f1
实例化的直接上下文中。最后,这也从重载集中删除了 f1
模板,再次导致由于重载集为空而导致重载解析失败,这次是在 is_f1_invocable
实例化的直接上下文中,这就是我们想要。
类似地,如果在实例化 f2
的主体时出现问题,那么我们需要修改 f2
的签名以解决这种可能性,并确保 SFINAE 在类似的环境中传播时尚
当然,您必须决定要走多远。在某些时候,您可能会决定此时确实要引起硬错误,而不是简单地从重载集中删除签名,将软错误传播到封闭的实例化中。