【问题标题】:Confusion with hard error in SFINAE与 SFINAE 中的硬错误混淆
【发布时间】:2018-01-16 23:39:09
【问题描述】:

关于以下代码(为方便起见,以下转载https://wandbox.org/permlink/nhx4pheijpTF1ohf

#include <type_traits>
#include <utility>

namespace foo_name {
template <typename T>
void foo();
template <>
void foo<int>();

template <typename T>
struct c_size;
template <>
struct c_size<int> : public std::integral_constant<int, 1> {};
} // namespace foo_name

template <typename Type>
class Foo {
public:
    template <typename T>
    static decltype(auto) impl(T&& t) {
        using foo_name::foo;
        return foo(std::forward<T>(t));
    }
};

class Something {};

template <typename Type, typename T = std::decay_t<Type>>
using EnableIfHasFoo = std::void_t<
    decltype(Foo<T>::impl(std::declval<T>())),
    decltype(foo_name::c_size<Type>::value)>;

template <typename Type, typename = std::void_t<>>
class Test {};
template <typename Type>
class Test<Type, EnableIfHasFoo<Type>> {};

int main() {
    static_cast<void>(Test<Something>{});
}

上面的代码以错误退出,因为Foo&lt;T&gt;::impl() 的实例化导致了一个硬错误并且在 SFINAE 上下文中不可用。但是这里奇怪的是,当你在EnableIfHasFoo中切换void_t中的东西的顺序(到下面的https://wandbox.org/permlink/at1KkeCraNwHGmUI),它会编译

template <typename Type, typename T = std::decay_t<Type>>
using EnableIfHasFoo = std::void_t<
    decltype(foo_name::c_size<Type>::value),
    decltype(Foo<T>::impl(std::declval<T>()))>;

现在问题是

  1. 为什么代码最初无法编译? Foo&lt;T&gt;::impl() 的实例化是在替换的上下文中,所以它应该可以工作吗?
  2. foo_name::foo(T) 代替void_t 的第一个参数将使它编译(参见https://wandbox.org/permlink/g3NaPFZxdUPBS7oj),为什么?添加一个额外的间接层如何使情况有所不同?
  3. 为什么void_t 中的顺序会有所不同,编译器是否会短路类型包中的表达式?

【问题讨论】:

    标签: c++ templates c++17 sfinae


    【解决方案1】:

    1) 和 2) 有相同的答案; SFINAE does not workreturn type deduction 因为函数体是 not in immediate context

    10 - 声明类型中带有占位符的函数模板的返回类型推导发生在 即使函数体包含具有非类型依赖的return 语句,也会实例化定义 操作数。 [ 注意: 因此,任何使用函数模板的特化都会导致隐式 实例化。此实例化产生的任何错误都不在函数的直接上下文中 类型并可能导致程序格式错误(17.8.2)。 ——结束注释 ]

    3) 是a more interesting question;短路是故意的,由[temp.deduct] 保证:

    7 - [...] 替换继续 按词法顺序并在遇到导致演绎失败的条件时停止。

    这种短路适用于 gcc、clang 和 ICC,但不幸的是 MSVC(截至 CL 19 2017 RTW)弄错了,for example

    template<class T> auto f(T t) -> decltype(t.spork) { return t.spork; }
    template<class T> auto g(T t) { return t.spork; }
    int x(...);
    template<class...> using V = void;
    template<class T> auto x(T t) -> V<decltype(f(t)), decltype(g(t))> {}
    int a = x(0);
    

    【讨论】:

    • 啊。很有意思!在我接受之前。我觉得我对“即时上下文”的规则有很多错误。你能把我链接到一个资源或什么东西上,我可以知道这个直接上下文到底是什么吗?
    • 另外,如果间接没有额外的层,为什么这段代码会编译。即我的第二个问题。那是因为返回类型扣除吗?如果 Foo::impl() 具有指定的返回类型。这段代码能用吗?
    • 我认为 @Curious stackoverflow.com/questions/15260685/… 很有帮助。如果Foo::impl() 具有指定的返回类型,则该返回类型在直接上下文中,因此被 SFINAE 覆盖。
    • 最后一个后续,为什么当我在我的代码中提供Foo::impl 的返回类型时,它会编译? foo_name::foo 是不是仍然在当前上下文之外,因为它推断了返回类型?
    • @Curious 重要的不是它做什么(计算返回类型),而是它发生的位置(在函数模板声明中,而不是在函数模板主体中)。函数模板实例化是使返回类型推导发生在原始案例中的直接上下文之外的原因。
    猜你喜欢
    • 2017-01-24
    • 2014-04-22
    • 1970-01-01
    • 2011-01-08
    • 2020-02-07
    • 2014-07-09
    • 1970-01-01
    • 1970-01-01
    • 2016-07-17
    相关资源
    最近更新 更多