gpt4 book ai didi

templates - 期望无限递归模板实例化?

转载 作者:行者123 更新时间:2023-12-04 02:55:04 24 4
gpt4 key购买 nike

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

设置如下。我有一个通用函数 foo(T)它将实现委托(delegate)给一个名为 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);
}
foo()出于 SFINAE 目的使用 decltype 尾随返回类型。 foo_impl 的默认实现没有定义任何调用运算符(operator)。接下来,我有一个类型特征来检测 foo()可以使用 T 类型的参数调用:
template <typename T>
struct has_foo
{
struct yes {};
struct no {};
template <typename T1>
static auto test(T1 x) -> decltype(foo(x),void(),yes{});
static no test(...);
static const bool value = std::is_same<yes,decltype(test(std::declval<T>()))>::value;
};

这只是通过表达式 SFINAE 实现类型特征的经典实现: has_foo<T>::value如果有效 foo_impl 则为真 T 存在特化,否则为假。最后,我对整数类型和浮点类型的实现仿函数有两个特化:
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_impl专门化,浮点类型的一种,我添加了额外的条件 foo()必须可用于类型 unsigned ( has_foo<unsigned>::value)。

我不明白的是为什么编译器(GCC 和 clang 两者)都接受以下代码:
int main()
{
foo(1.23);
}

据我了解,当 foo(1.23)被称为以下应该发生:
  • foo_impl的特化for 整数类型被丢弃,因为 1.23不是整数,所以只有 foo_impl 的第二个特化被认为;
  • foo_impl 的第二个特化的启用条件包含 has_foo<unsigned>::value ,即编译器需要检查 foo()可以在类型 unsigned 上调用;
  • 为了检查 foo()可以在类型 unsigned 上调用,编译器需要再次选择 foo_impl 的特化在可用的两个中;
  • 此时,foo_impl 的第二个特化的启用条件编译器再次遇到条件 has_foo<unsigned>::value .
  • 转到 3。

  • 然而,GCC 5.4 和 Clang 3.8 似乎都乐于接受该代码。见这里: http://ideone.com/XClvYT

    我想了解这里发生了什么。我是否误解了某些东西并且递归被其他一些效果阻止了?或者我是否触发了某种未定义/实现定义的行为?

    最佳答案

    has_foo<unsigned>::value是一个非依赖表达式,因此它立即触发 has_foo<unsigned> 的实例化(即使从未使用过相应的特化)。

    相关规则为[temp.point]/1:

    For a function template specialization, a member function template specialization, or a specialization for a member function or static data member of a class template, if the specialization is implicitly instantiated because it is referenced from within another template specialization and the context from which it is referenced depends on a template parameter, the point of instantiation of the specialization is the point of instantiation of the enclosing specialization. Otherwise, the point of instantiation for such a specialization immediately follows the namespace scope declaration or definition that refers to the specialization.



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

    The program is ill-formed, no diagnostic required, if:
    - [...]
    - a hypothetical instantiation of a template immediately following its definition would be ill-formed due to a construct that does not depend on a template parameter, or
    - the interpretation of such a construct in the hypothetical instantiation is different from the interpretation of the corresponding construct in any actual instantiation of the template.



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

    因此, has_foo 的实例化点在浮点部分特化内发生在该特化的声明点之前,即 > 之后。根据 [basic.scope.pdecl]/3 的部分特化:

    The point of declaration for a class or class template first declared by a class-specifier is immediately after the identifier or simple-template-id (if any) in its class-head (Clause 9).



    因此,当调用 foo来自 has_foo<unsigned>查找 foo_impl 的部分特化,它根本找不到浮点特化。

    关于您的示例的其他一些说明:

    1) 使用cast-to- void逗号运算符:
    static auto test(T1 x) -> decltype(foo(x),void(),yes{});

    这是一个糟糕的模式。 operator,仍然会为逗号运算符执行查找,其中它的操作数之一是类或枚举类型(即使它永远不会成功)。这可能会导致执行 ADL [允许实现但不需要跳过此操作],这会触发返回类型为 foo 的所有关联类的实例化(特别是,如果 foo 返回 unique_ptr<X<T>>,这可能会触发 X<T> 的实例化,并且如果该实例化在此翻译单元中不起作用,则可能导致程序格式错误)。您应该更愿意将用户定义类型的逗号运算符的所有操作数转换为 void :
    static auto test(T1 x) -> decltype(void(foo(x)),yes{});

    2)SFINAE成语:
    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;

    在一般情况下,这不是正确的 SFINAE 模式。这里有几个问题:
  • 如果 T是不能作为参数传递的类型,例如 void , 你会触发一个硬错误而不是 value评估为 false符合预期
  • 如果 T是无法形成引用的类型,您再次触发硬错误
  • 你检查是否foo可应用于 remove_reference<T> 类型的左值即使T是一个右值引用

  • 更好的解决方案是将整个检查放入 yes test 的版本而不是拆分 declval部分到 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;

    最后,如果您移动上述 test 的声明,您的类型特征将在编译时更快地计算并使用更少的内存。 has_foo 的定义之外(可能进入一些辅助类或命名空间);这样,它们不需要为每次使用 has_foo 重复实例化一次。 .

    关于templates - 期望无限递归模板实例化?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39059511/

    24 4 0