C ++模板中的罗素悖论 [英] Russell's paradox in C++ templates

查看:113
本文介绍了C ++模板中的罗素悖论的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑此程序:

#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.我们试图通过使其可转换(启用转换构造函数)来创建一个悖论.

If some type barber is not convertible to russell. we attempt to create a paradox by making it convertible (enabling a converting constructor).

输出是00,其中包含三个流行的编译器,尽管构造函数显然可以正常工作.

The output is 00 with three popular compilers, though constructors are evidently working.

我怀疑行为应该是不确定的,但在标准中找不到任何内容.

I suspect the behaviour should be undefined, but cannot find anything in the standard.

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

What should the output of this program be, and why?

推荐答案

在重载解析期间,模板参数推导必须实例化默认参数以获得完整的模板参数集,以使用实例化功能模板.因此,必须实例化is_convertible<int, russell>,该实例在内部调用重载解析. russell中的构造函数模板在默认模板参数的实例化上下文中.

During overload resolution, template argument deduction must instantiate the default argument to obtain a complete set of template arguments to instantiate the function template with (if possible). Hence the instantiation of is_convertible<int, russell> is necessitated, which internally invokes overload resolution. The constructor template in russell is in scope in the instantiation context of the default template argument.

问题的关键在于is_convertible<int, russell>::value评估russell的默认模板参数,本身命名为 is_convertible<int, russell>::value.

The crux is that is_convertible<int, russell>::value evaluates the default template argument of russell, which itself names is_convertible<int, russell>::value.

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

核心问题287 (未采用)分辨率似乎是主要编译器事实上的规则.因为实例化点就在实体之前,所以value的声明不在我们评估其初始化程序的范围之内.因此,我们的构造函数发生替换失败,并且main中的is_convertible产生了false. 问题287阐明了哪些声明在范围内,哪些不在范围内,即value.

core issue 287's (unadopted) resolution seems to be the de facto rule abode by major compilers. Because the point of instantiation comes right before an entity, value's declaration is not in scope while we're evaluating its initialiser; hence our constructor has a substitution failure and is_convertible in main yields false. Issue 287 clarifies which declarations are in scope, and which are not, namely value.

Clang和GCC在如何处理这种情况方面确实略有不同.以以下示例为例,该示例具有特征的自定义透明实现:

Clang and GCC do slightly differ on how they treat this situation. Take this example with a custom, transparent implementation of the 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是正确的,因为

Clang translates this silently. GCC complains about an infinite recursion chain: it seems to argue that value is indeed in scope in the recursive instantiation of the default argument, and so proceeds to instantiate the initializer of value again and again. However, arguably Clang is in the right, since both the current and the drafted relevant phrase in [temp.point]/4 mandate that the PoI is before the nearest enclosing declaration. I.e. that very declaration is not considered to be part of the partial instantiation (yet). Kinda makes sense if you consider the above scenario. Workaround for GCC: employ a declaration form in which the name is not declared until after the initializer is instantiated.

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

这也会与 GCC一起编译.

这篇关于C ++模板中的罗素悖论的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆