【问题标题】:Russell's paradox in C++ templates [duplicate]C++ 模板中的罗素悖论 [重复]
【发布时间】:2017-08-27 16:45:17
【问题描述】:

考虑这个程序:

#include <iostream>
#include <type_traits>

using namespace std;

struct russell {
    template <typename barber, 
              typename = typename enable_if<!is_convertible<barber, russell>::value>::type>
    russell(barber) {}
};

russell verify1() { return 42L; }
russell verify2() { return 42; }

int main ()
{
    verify1();
    verify2();
    cout << is_convertible<long, russell>::value;
    cout << is_convertible<int, russell>::value;
    return 0;
}

如果某些类型 barber 不能转换为 russell。我们试图通过使其可转换(启用转换构造函数)来创建一个悖论。

输出是00,带有三个流行的编译器,尽管构造函数显然在工作。

我怀疑行为应该是未定义的,但在标准中找不到任何东西。

这个程序的输出应该是什么,为什么?

【问题讨论】:

  • @Barry 是的,这看起来很受骗......
  • @Barry 我偷偷地添加了我的答案,因为我在看了很长时间并最终完成答案后感到恐慌。如果您认为这更明智,我可以重写它以适合您的问题并将其发布到那里。
  • @Columbo 我不知道什么更明智,但我很确定无论你决定做什么,我都会投赞成票。甚至可能想扭转欺骗的方向?
  • @Barry 我故意再次欺骗它,因为你的问题有不可否认的相似之处。我会保留它现在的样子。

标签: c++ language-lawyer template-meta-programming


【解决方案1】:

在重载决议期间,模板实参推导必须实例化默认实参,以获得完整的模板实参集来实例化函数模板(如果可能)。因此is_convertible&lt;int, russell&gt; 的实例化是必要的,它在内部调用重载决议。 russell 中的构造函数模板在默认模板参数的实例化上下文范围内。

关键在于is_convertible&lt;int, russell&gt;::value 评估russell 的默认模板参数,它本身命名为 is_convertible&lt;int, russell&gt;::value

is_convertible<int, russell>::value
              |
              v
russell:russell(barber)
              |
              v
is_convertible<int, russell>::value (not in scope)

core issue 287 的(未采用)解析似乎是主要编译器遵守的事实上的规则。因为实例化点就在实体之前,所以value 的声明不在我们评估其初始化程序的范围内;因此我们的构造函数出现替换失败,main 中的is_convertible 产生false。 第 287 期澄清了哪些声明在范围内,哪些不在范围内,即value

Clang 和 GCC 在处理这种情况的方式上确实略有不同。以该 trait 的自定义、透明实现为例:

#include <type_traits>

template <typename T, typename U>
struct is_convertible
{
    static void g(U);

    template <typename From>
    static decltype(g(std::declval<From>()), std::true_type{}) f(int);
    template <typename>
    static std::false_type f(...);

    static const bool value = decltype(f<T>()){};
};

struct russell
{
    template <typename barber,
              typename = std::enable_if_t<!is_convertible<barber, russell>::value>>
    russell(barber) {}
};

russell foo() { return 42; }

int main() {}

Clang 默默地翻译这个。 GCC 抱怨无限递归链:似乎认为value 确实在默认参数的递归实例化范围内,因此一次又一次地实例化value 的初始化程序。然而,可以说 Clang 是正确的,因为 [temp.point]/4 中当前和起草的相关短语都要求 PoI 最近的封闭声明之前。 IE。该声明不被视为部分实例化的一部分(尚未)。如果您考虑上述情况,那是有道理的。 GCC 的解决方法:使用一种声明形式,其中在初始化器实例化之前不声明名称。

enum {value = decltype(f<T>()){}};

这与 GCC as well 编译。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-07-02
    • 1970-01-01
    • 2021-11-27
    • 1970-01-01
    • 2020-01-28
    • 1970-01-01
    • 2015-10-28
    • 1970-01-01
    相关资源
    最近更新 更多