Templates 需要无限递归模板实例化吗?

Templates 需要无限递归模板实例化吗?,templates,c++11,c++14,sfinae,Templates,C++11,C++14,Sfinae,我试图理解为什么一段模板元编程不是生成无限递归。我试着尽可能减少测试用例,但仍然有一些设置涉及,所以请耐心听我说:) 设置如下所示。我有一个泛型函数foo(T),它通过调用操作符将实现委托给一个名为foo_impl的泛型函数,如下所示: template <typename T, typename = void> struct foo_impl {}; template <typename T> inline auto foo(T x) -> decltype(f

我试图理解为什么一段模板元编程不是生成无限递归。我试着尽可能减少测试用例,但仍然有一些设置涉及,所以请耐心听我说:)

设置如下所示。我有一个泛型函数
foo(T)
,它通过调用操作符将实现委托给一个名为
foo_impl
的泛型函数,如下所示:

template <typename T, typename = void>
struct foo_impl {};

template <typename T>
inline auto foo(T x) -> decltype(foo_impl<T>{}(x))
{
    return foo_impl<T>{}(x);
}
这只是通过表达式SFINAE实现类型特征的经典实现: 如果对
T
存在有效的
foo\u impl
专业化,则
has\u foo::value
为真,否则为假。最后,我对整型和浮点型的实现函子进行了两种专门化:

template <typename T>
struct foo_impl<T,typename std::enable_if<std::is_integral<T>::value>::type>
{
    void operator()(T) {}
};

template <typename T>
struct foo_impl<T,typename std::enable_if<has_foo<unsigned>::value && std::is_floating_point<T>::value>::type>
{
    void operator()(T) {}
};
据我所知,当调用
foo(1.23)
时,应发生以下情况:

  • 整数类型的
    foo_impl
    专业化被放弃,因为
    1.23
    不是整数,所以只考虑
    foo_impl
    的第二专业化
  • foo_impl
    的第二个专门化的启用条件包含
    具有\u foo::value
    ,也就是说,编译器需要检查是否可以对类型
    unsigned
    调用
    foo()
  • 为了检查类型
    unsigned
    是否可以调用
    foo()
    ,编译器需要再次在两个可用的类型中选择
    foo_impl
    的专门化
  • 此时,在第二次专门化
    foo\u impl
    的启用条件下,编译器再次遇到条件
    has\u foo::value
  • 转到3
  • 然而,GCC 5.4和Clang 3.8似乎都乐于接受该代码。请看这里:


    我想知道这里发生了什么。我是不是误会了什么,递归被其他一些效果阻止了?或者可能是我触发了某种未定义/实现定义的行为?

    它实际上不是UB。但它确实向你展示了TMP是多么复杂

    这不会无限递归的原因是因为完整性

    template <typename T>
    struct foo_impl<T,typename std::enable_if<std::is_integral<T>::value>::type>
    {
        void operator()(T) {}
    };
    
    // has_foo here
    
    template <typename T>
    struct foo_impl<T,typename std::enable_if<has_foo<unsigned>::value && std::is_floating_point<T>::value>::type>
    {
        void operator()(T) {}
    };
    
    模板
    结构foo_impl
    {
    void运算符()(T){}
    };
    //阿福在这儿吗
    模板
    结构foo_impl
    {
    void运算符()(T){}
    };
    
    当你调用
    foo(3.14),您实例化
    has\u foo
    。这反过来会影响到
    foo\u impl

    如果
    是积分
    ,则启用第一个。显然,这是失败的

    现在考虑第二个
    foo_impl
    。试图实例化它时,编译器会看到
    有\u foo::value

    回到实例化
    foo_impl
    foo_impl

    第一个
    foo_impl
    是匹配项

    第二个是考虑。
    enable\u if
    包含的
    具有\u foo
    ——编译器已经尝试实例化的对象

    因为它当前正在实例化,所以它是不完整的,不考虑这种专门化

    递归停止,
    has_foo::value
    为true,代码段正常工作


    那么,你想知道它是如何在标准中体现出来的吗?好的

    [14.7.1/1]如果在实例化点([temp.point])声明了类模板,但未定义,则实例化会产生不完整的类类型

    (不完整)

    has_foo::value
    是一个非依赖表达式,因此它会立即触发
    has_foo
    的实例化(即使从未使用相应的专门化)

    相关规则为[温度点]/1:

    对于函数模板专用化、成员函数模板专用化或类模板的成员函数或静态数据成员专用化,如果专门化是隐式实例化的,因为它是从另一个模板专门化中引用的,并且从中引用它的上下文取决于模板参数,那么专门化的实例化点就是封闭专门化的实例化点。否则,此类专门化的实例化点将紧跟在引用该专门化的命名空间范围声明或定义之后

    (请注意,我们在这里是非依赖的情况)和[temp.res]/8:

    节目是 格式错误,无需诊断,如果:
    -[…]
    -由于构造不依赖于模板参数或
    -在假设的实例化中对此类构造的解释不同于在模板的任何实际实例化中对相应构造的解释

    这些规则旨在为实现提供在上面示例中出现的位置实例化
    has_foo
    的自由,并为其提供与在那里实例化时相同的语义。(请注意,这里的规则实际上有细微的错误:被另一个实体的声明引用的实体的实例化点实际上必须紧跟在该实体之前,而不是紧跟在该实体之后。这已被报告为一个核心问题,但由于该列表已经有一段时间没有更新,因此尚未出现在问题列表上。)

    因此,
    在浮点部分专门化中的实例化点在该专门化的声明点之前出现,该声明点位于根据[basic.scope.pdecl]/3的部分专门化的
    之后:

    类说明符首先声明的类或类模板的声明点紧跟在其类头中的标识符或简单模板id(如果有)之后(第9条)

    因此,当从
    int main()
    {
        foo(1.23);
    }
    
    template <typename T>
    struct foo_impl<T,typename std::enable_if<std::is_integral<T>::value>::type>
    {
        void operator()(T) {}
    };
    
    // has_foo here
    
    template <typename T>
    struct foo_impl<T,typename std::enable_if<has_foo<unsigned>::value && std::is_floating_point<T>::value>::type>
    {
        void operator()(T) {}
    };
    
    static auto test(T1 x) -> decltype(foo(x),void(),yes{});
    
    static auto test(T1 x) -> decltype(void(foo(x)),yes{});
    
    template <typename T1>
    static auto test(T1 x) -> decltype(void(foo(x)),yes{});
    static no test(...);
    static const bool value = std::is_same<yes,decltype(test(std::declval<T>()))>::value;
    
    template <typename T1>
    static auto test(int) -> decltype(void(foo(std::declval<T1>())),yes{});
    template <typename>
    static no test(...);
    static const bool value = std::is_same<yes,decltype(test<T>(0))>::value;
    
    // elsewhere
    template<int N> struct rank : rank<N-1> {};
    template<> struct rank<0> {};
    
    
    template <typename T1>
    static no test(rank<2>, std::enable_if_t<std::is_same<T1, double>::value>* = nullptr);
    template <typename T1>
    static yes test(rank<1>, decltype(foo(std::declval<T1>()))* = nullptr);
    template <typename T1>
    static no test(rank<0>);
    static const bool value = std::is_same<yes,decltype(test<T>(rank<2>()))>::value;