Russell在C++模板中的悖论

n. *_* m. 20 c++ language-lawyer template-meta-programming

考虑这个程序:

#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;
}
Run Code Online (Sandbox Code Playgroud)

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

输出00有三个流行的编译器,虽然构造函数显然在工作.

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

该计划的输出应该是什么,为什么?

Col*_*mbo 8

在重载解析期间,模板参数推导必须实例化默认参数以获取一组完整的模板参数,以便使用(如果可能)实例化函数模板.因此,实例化is_convertible<int, russell>是必要的,其内部调用重载决策.构造函数模板位于russell默认模板参数的实例化上下文中.

关键是is_convertible<int, russell>::value评估默认模板参数russell,它自己命名 is_convertible<int, russell>::value.

is_convertible<int, russell>::value
              |
              v
russell:russell(barber)
              |
              v
is_convertible<int, russell>::value (not in scope)
Run Code Online (Sandbox Code Playgroud)

核心问题287(未采用的)决议似乎是主要编制者事实上的规则.因为实例化的要点恰好在实体之前,value所以当我们评估其初始化时,声明不在范围内; 因此,我们的构造函数有一个替换故障,is_convertiblemain收益率false.问题287阐明哪些声明属于范围,哪些声明不属于,即value.

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

#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() {}
Run Code Online (Sandbox Code Playgroud)

Clang默默地翻译了这个.GCC抱怨无限递归链:它似乎认为value在默认参数的递归实例化中确实存在范围,因此继续实例化value一次又一次的初始化器.但是,可以说Clang是正确的,因为[temp.point]/4中当前和起草的相关短语都要求PoI 最近的封闭声明之前.即,非常声明不被认为是部分实例化的一部分(尚未).如果考虑上述情况,Kinda会有意义.GCC的解决方法:使用声明表单,在声明初始化程序实例化之前,不会声明该名称.

enum {value = decltype(f<T>()){}};
Run Code Online (Sandbox Code Playgroud)

这也与GCC一起编译.