【问题标题】:Tuple works with nullptr but doesnt with NULL元组适用于 nullptr 但不适用于 NULL
【发布时间】:2019-10-11 12:41:57
【问题描述】:

我有以下代码,其中 std::tuple 适用于 nullptr 但不适用于 NULL。

#include <iostream>
#include <tuple>

int main()
{
tuple<int*> t1, t2;

t1 = std::make_tuple(NULL);
t2 = std::make_tuple(nullptr);
}

使用 C++11 编译时,代码在使用 nullptr 时有效,但在使用 NULL 时会出现以下错误。

In file included from tuple.cpp:2:
/usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/tuple:447:8: error: assigning to 'int *' from incompatible type 'long'
            = std::forward<_UHead>(_Tuple_impl<_Idx, _UHead>::_M_head(__in));
              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/tuple:575:36: note: in instantiation of function template specialization 'std::_Tuple_impl<0, int *>::operator=<long>' requested here
          static_cast<_Inherited&>(*this) = std::move(__in);
                                          ^
tuple.cpp:13:8: note: in instantiation of function template specialization 'std::tuple<int *>::operator=<long, void>' requested here
    t1 = std::make_tuple(NULL);
       ^
1 error generated.

这里,NULL的类型是long int,并且元组足够严格,不接受。

我们怎样才能使它也使用 NULL 工作,因为我们的客户说它在与 nvcc 编译器一起使用时可以工作(当他们在 CUDA 代码中使用上述 sn-p 时),但它不能工作。

【问题讨论】:

  • 简单的反应是停止使用NULLnullptr 在每个地方都可以工作 NULL 在没有问题的地方都可以工作,因为你没有正确使用NULL,所以应该修复它。
  • @NathanOliver 谢谢。但是如果他们仍然想使用NULL,有没有办法支持。我知道将其类型转换为 int* 是可行的,但我想知道 nvcc 编译器如何处理这种情况,因为他们说与 nvcc 一起使用时不会发生此错误。
  • NVCC 可以像(void*)0 一样定义NULL,这可以解释它为什么起作用。它不是合法的 C++,但没有实现是 100% 合法的。
  • 谢谢。一定是在nvcc中定义NULL的方式与使用的编译器不同。

标签: c++ tuples


【解决方案1】:

我们怎样才能让它也使用 NULL 来工作

您可以使用std::make_tuple&lt;int*&gt;(NULL) 或简单地使用std::tuple&lt;int*&gt;(NULL)。但更喜欢使用nullptr

您面临的问题是nullptr 被引入该语言的原因。在引入之前,唯一的标准空指针文字是 0(0L 等也是有效的),这就是 NULL 扩展1 到的内容。

问题在于 0 不仅是一个指针字面量,而且实际上也是一个整数字面量。在模板类型参数推导中优先考虑的是文字的整数性质,std::make_tuple(NULL) 可能会导致std::tuple&lt;int&gt;std::tuple&lt;long&gt;,具体取决于NULL 的确切定义。而且这些元组不能隐式转换为std::tuple&lt;int*&gt;


1 从技术上讲,既然nullptr 是语言,NULL 也可以扩展到该语言。但这不太可能发生。

【讨论】:

  • 我想知道如果它们只是更改为 NULL 宏以扩展为 std::nullptr 是否会产生问题?据推测,它可能会破坏一些使用 NULL 作为整数值的代码,但可以说该代码已经被破坏了。
  • @JeremyFriesner 这不是std::nullptr,而是关键字nullptr。至于为什么更改NULL 会成为问题,请参阅“Hyrum 定律”。它会破坏任何现有的代码,这些代码假设NULL 是一个整数,例如str[last] = NULL。这样做很愚蠢,但愚蠢的代码确实存在。
  • 几年前他们在 MSVS 的库代码库中尝试了#define NULL nullptr,编译器爆炸了。不幸的是,NULL 的错误使用很普遍。
  • 为什么是std::make_tuple&lt;int*&gt;(NULL)?使用make_tuple 的唯一原因是推断参数,明确指定它们会破坏目的。 std::tuple&lt;int*&gt;(NULL) 做同样的事情但不那么复杂......
  • @SatyanveshD std::make_tuple(nullptr) 是更好的选择。在实践中定义 NULL 不是一种选择。
【解决方案2】:

nullptr 有自己的类型 std::nullptr_t 的原因之一是文字 0 很特殊:

[conv.ptr]/1

空指针常量 是一个整数文字 ([lex.icon]),其值为 0 或类型为 std​::​nullptr_­t 的纯右值。 空指针常量可以转换为指针类型;结果是该类型的空指针值([basic.compound]),并且可以与对象指针或函数指针类型的所有其他值区分开来。 [...]

您可以将0(或0l)转换为任何指针类型,但您不能将1(或1l)或任何其他整数转换为任何指针类型(无需显式转换,即是)。

只要您将 NULL 定义为文字 (#define NULL 0l) 的宏并在需要进行此转换的地方直接使用它,就可以正常工作。但这使得完美转发不可能:一个函数(例如std::make_tuple)在传递NULL时将T&amp;&amp;推导出为long,失去了0的特殊属性字面意思。

作为一种解决方案,nullptr 被创建并赋予其自己的类型 std::nullptr_t,并具有类似于 0 文字的隐式转换。这意味着nullptr(但不是NULL)的完美转发保留了这种“特殊性”。

底线:如果您想使用 C++11 功能(尤其是完美转发),例如 std::make_X,请使用 nullptr 而不是 NULL(或 0)。

没有“让它发挥作用”。在符合标准的编译器(参见[support.types.nullptr]/2 和脚注)中使用NULLmake_tuple 将导致这个确切的问题。不要使用NULL 或不要使用make_tuple,这是您问题的唯一答案。

【讨论】:

  • 感谢您提供详细信息。是的,nullptr 有效,但由于他们使用的是 NULL,所以不得不问这个。
【解决方案3】:

t1 和 t2 是否必须是同一类型的对象?

a) 是的情况

t2 = std::make_tuple(static_cast<int*>(NULL));

b) 没有案例

std::tuple<decltype(NULL)> t2;

【讨论】:

  • 是的,在这种情况下,我只是使用 t1 和 t2 来显示差异。我相信 t2 = std::make_tuple(nullptr);和 std::tuple t2;是一样的。第一个案例应该对我有用。谢谢。
【解决方案4】:

似乎使用的编译器将宏NULL定义为空指针常量0L

所以在这个声明中

t1 = std::make_tuple(NULL);

正确表达式的类型是std::tuple&lt;long&gt;

尝试运行以下演示程序

#include <iostream>
#include <iomanip>
#include <tuple>
#include <type_traits>

int main() 
{
    auto t = std::make_tuple( NULL );

    std::cout << std::boolalpha 
              << std::is_same<std::tuple<long>, decltype( t )>::value << '\n';

    return 0;
}

它的输出是

true

所以推导出元组的参数类型为long

其实这个说法

t1 = std::make_tuple(NULL);

语义上等价于以下代码

#include <iostream>

int main() 
{
    int *p;
    long v = 0L;

    p = v;

    return 0;
} 

编译器会发出类似这样的错误

prog.cpp:8:6: error: invalid conversion from ‘long int’ to ‘int*’ [-fpermissive]
  p = v;
      ^

您需要将NULL 显式转换为int * 类型。例如

t1 = std::make_tuple( ( int * )NULL);

尽管无论如何最好使用nullptr。这就是 C++ 中引入指针字面量nullptr 的原因。

那么你使用的是指针字面量nullptr,那么它就可以隐式地转换为int * 类型。在这种情况下,分配是正确的。考虑

#include <iostream>

int main() 
{
    int *p;
    std::nullptr_t v = nullptr;

    p = v;

    return 0;
} 

【讨论】:

  • 问题是如何让它与NULL 一起工作,而不是为什么不能。 OP 知道为什么不这样做。
  • @molbdnilo 谢谢。这确实是一个错字。:)
  • 感谢您的解释和示例。你的第一句话是真的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多