Tuple可以与nullptr一起使用,但无法与NULL一起使用。

3
我有以下代码,其中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代码中使用上述片段),它可以工作,但否则不起作用。


5
简单回答是停止使用 NULLnullptr 可以在每个 NULL 可以使用的地方使用,并且在不能使用 NULL 的地方,你应该修复代码,因为你没有正确使用 NULL - NathanOliver
@NathanOliver 谢谢。但是如果他们仍然想使用NULL,有没有支持的方法?我知道将其强制转换为int*可以解决问题,但我想知道nvcc编译器如何处理这种情况,因为他们说在与nvcc一起使用时不会出现此错误。 - Satyanvesh D
2
NVCC可以像(void*)0一样定义NULL,这就解释了为什么它能工作。虽然这不是合法的C++,但没有任何实现是100%合法的。 - NathanOliver
谢谢。这可能是因为 nvcc 的 NULL 定义方式与所使用的编译器不同。 - Satyanvesh D
4个回答

7
我们如何使用NULL使它生效?
您可以使用std::make_tuple<int*>(NULL)或简单地使用std::tuple<int*>(NULL)。但最好使用nullptr
您面临的问题是nullptr被引入语言的原因。在其引入之前,唯一的标准空指针字面量是0(也可以是0L等),而这就是NULL的展开方式。1 问题在于,0不仅是指针文字,实际上还是整数文字。而它的整数本质在模板类型参数推断和std::make_tuple(NULL)中具有优先权,可能会导致std::tuple<int>std::tuple<long>,具体取决于NULL的确切定义。 而这些元组不能隐式转换为std::tuple<int*>
1 从技术上讲,现在nullptr已经加入到语言中,NULL也可以展开成它。 但这很不可能发生。

我想知道如果他们只是将NULL宏更改为扩展为std::nullptr是否会产生问题?可能会破坏一些将NULL用作整数值的代码,但可以说这些代码已经存在问题。 - Jeremy Friesner
3
他们在几年前尝试在MSVS的库代码中使用#define NULL nullptr,但是编译器崩溃了。不幸的是,不正确地使用NULL相当普遍。 - NathanOliver
为什么要使用 std::make_tuple<int*>(NULL)?使用 make_tuple 的唯一原因是为了推断参数,显式指定它们会失去这个目的。std::tuple<int*>(NULL) 也可以实现相同的效果,但更简单明了... - Max Langhof
1
std::make_tuple(nullptr) 是一个更好的选择。在实践中定义 NULL 不是一个选项。 - eerorika
@SatyanveshD 如上所述,如果您可以编写 std::make_tuple<int*>(NULL),那么您也可以编写 std::tuple<int*>(NULL)。前者与后者相比没有任何优势。 - Max Langhof
显示剩余4条评论

2
nullptr 有自己的类型 std::nullptr_t 的一个原因是,字面值 0 是特殊的:

[conv.ptr]/1

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

你可以将 0(或 0l)转换为任何指针类型,但不能将 1(或 1l)或任何其他整数转换为任何指针类型(除非显式强制转换)。
只要你将 NULL 定义为字面值的宏(#define NULL 0l)并在需要进行此转换的地方直接使用它,这就很好用了。但这使得“完美转发”成为不可能:当传递 NULL 时,取 T&& 的函数(例如 std::make_tuple)将推断类型为 long,从而失去了 0 字面值的特殊属性。
作为解决方案,创建了 nullptr 并赋予其自己的类型 std::nullptr_t,具有与 0 字面值相似的隐式转换。这意味着 nullptr(但不是 NULL)的完美转发保留了这种“特殊性”。 Bottom line: 如果想要使用 C++11 特性(特别是完美转发)如 std::make_X,请使用 nullptr 而不是 NULL(或 0)。
在符合标准的编译器中(请参见 [support.types.nullptr]/2 和脚注),使用 NULLmake_tuple 将导致此确切问题。不要使用 NULL 或不要使用 make_tuple,这是你的唯一答案。

感谢提供详细信息。是的,nullptr可以使用,但由于他们使用了NULL,所以不得不问一下。 - Satyanvesh D

1
似乎使用的编译器将宏NULL定义为空指针常量0L。因此,在该语句中,
t1 = std::make_tuple(NULL);

右表达式的类型是std::tuple<long>。 尝试运行以下演示程序。
#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一起工作,而不是为什么它不工作。提问者知道为什么它不工作。 - NathanOliver
@molbdnilo 谢谢。确实是个打字错误。:) - Vlad from Moscow
感谢您的解释和示例。您的第一句话是正确的。 - Satyanvesh D

1

请问t1和t2必须是同一类型的对象吗?

a) 是的

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

b) case NO

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

是的,在这种情况下,我只是使用t1和t2来展示它们之间的区别。我相信t2 = std::make_tuple(nullptr); 和std::tuple<decltype(NULL)> t2; 是一样的。第一种情况对我应该可行。谢谢。 - Satyanvesh D

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接