模板函数无法将 'int' 转换为 nullptr_t

Templated function can't convert 'int' to nullptr_t

TL;DR:

为什么模板化函数不能访问非模板化函数可以访问的相同转换?

struct A {
    A(std::nullptr_t) {}
};

template <typename T>
A makeA(T&& arg) {
    return A(std::forward<T>(arg));
}

void foo() {
    A a1(nullptr); //This works, of course
    A a2(0); //This works
    A a3 = makeA(0); //This does not
}

背景

我正在尝试编写一些模板化包装器 classes 以围绕现有类型使用,其目标是成为替代品,而无需重写使用现在包装的值的现有代码。

一个我无法理解的特殊情况如下:我们有一个 class 可以从 std::nullptr_t 构造(这里称为 A),因此,有很多代码库中有人将零分配给实例的位置。

但是,尽管转发了构造函数,但包装器不能分配零。我做了一个非常相似的例子,它在没有使用实际包装器的情况下重现了这个问题 class - 一个简单的模板化函数就足以说明这个问题。

我想继续允许能够分配零的语法 - 这不是我最喜欢的,但尽量减少迁移到更新代码的摩擦通常是让人们参与其中的必要条件使用它们。

我也不想添加一个接受除零以外的任何 int 的构造函数,因为这非常荒谬,以前是不允许的,它应该在编译时继续被捕获。

如果这样的事情不可能,找到一个解释会让我很满意,因为据我所知,这对我来说毫无意义。

此示例在 VC++(虽然 Intellisense 似乎可以接受...)、Clang 和 GCC 中具有相同的行为。理想情况下,一个解决方案也适用于所有 3 个(4 个带有智能感知)编译器。

更直接适用的例子如下:

struct A {
    A(){}
    A(std::nullptr_t) {}
};

template <typename T>
struct Wrapper {
    A a;
    Wrapper(const A& a):a (a) {}

    template <typename T>
    Wrapper(T&& t): a(std::forward<T>(t)){}

    Wrapper(){}
};

void foo2() {
    A a1;
    a1 = 0; // This works

    Wrapper<A> a2;
    a2 = 0; //This does not
}

Why has the compiler decided to treat the zero as an int?

因为是整数

文字0是文字。文字开始做有趣的事情。字符串文字可以转换为const char*const char[N],其中N是字符串的长度+NUL终止符。文字 0 也可以做一些有趣的事情;它可用于用 NULL 指针常量初始化指针。并且可以用来初始化nullptr_t类型的对象。当然,它可以用来创建一个整数。

但是一旦它作为参数传递,它就不再是一个神奇的编译器构造。它成为具有具体类型的实际 C++ 对象。当谈到模板参数推导时,它得到最明显的类型:int.

一旦它变成 int,它 就不再是 字面量 0,其行为与任何其他 int 完全一样。除非它在 ​​constexpr 上下文中使用(如您的 int(0)),编译器可以在其中确定它确实是文字 0,因此可以呈现其神奇的属性。函数参数永远不会 constexpr,因此它们不能参与其中。

见[conv.ptr]/1:

A null pointer constant is an integer literal with value zero or a prvalue of type std::nullptr_t. A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type [...]

所以整型字面量0可以转换为空指针。但是,如果您尝试将其他一些不是文字的整数值转换为指针类型,则上述引用不适用。事实上,没有其他从整数到指针的隐式转换(因为 none 在 [conv.ptr] 中列出),所以你的代码失败了。

注意:[expr.reinterpret.cast]/5 涵盖了显式转换。