有没有什么好的方法可以解决nullptr_t和指针重载之间的歧义?
Is there a good way to resolve the ambiguity between nullptr_t and pointer overloads?
在 C++ 中使用文字 0
时,编译器无法区分函数的指针和 nullptr_t
重载。
说明问题的代码:
struct Bar {};
void foo(Bar*) {
std::cout << "Bar*" << std::endl;
}
void foo(std::nullptr_t) {
std::cout << "nullptr_t" << std::endl;
}
TEST(NullPtrTest, ambiguity) {
foo(nullptr); // OK
foo(0); // ERROR
}
与 Visual Studio 2019:
error C2668: '`anonymous-namespace'::foo': ambiguous call to overloaded function
message : could be 'void `anonymous-namespace'::foo(std::nullptr_t)'
message : or 'void `anonymous-namespace'::foo(`anonymous-namespace'::Bar *)'
message : while trying to match the argument list '(int)'
使用 GCC 9:
Test.cpp: In member function ‘virtual void {anonymous}::NullPtrTest_ambiguity_Test::TestBody()’:
Test.cpp:425:8: error: call of overloaded ‘foo(int)’ is ambiguous
425 | foo(0); // ERROR
| ^
Test.cpp:415:6: note: candidate: ‘void {anonymous}::foo({anonymous}::Bar*)’
415 | void foo(Bar*) {
| ^~~
Test.cpp:419:6: note: candidate: ‘void {anonymous}::foo(std::nullptr_t)’
419 | void foo(std::nullptr_t) {
| ^~~
解决这个问题的好方法是什么?
我不想做的事情:
- 将
0
文字替换为 nullptr
无处不在。我们遗留代码库中的实例太多了。
- 添加
int
或 long
(或类似)重载。由于 0
将是唯一有效的整数值,因此您必须添加运行时检查,这会很难看。
- 删除
nullptr_t
重载并在运行时检查指针重载中的值。这行得通,而且并不糟糕,但它阻止了我们对空常量进行优化实现。
谢谢!
澄清:
- 我们正在使用 C++14。但是,这些函数被许多早于 C++11 的代码使用。
- 特定用例是在使用
nullptr
或 0
时提供优化的 constexpr
实现。通用指针实现仍然需要检查空值。 nullptr_t
重载并不是真正必要的,但它会很好。
- 我问这个主要是想看看有没有我没有考虑的选项。
模板不在排除列表中 - 将其设为模板,限制为仅接受 Bar*
:
#include <iostream>
#include <type_traits>
struct Bar {};
template <typename T>
typename std::enable_if<std::is_same_v<T, Bar>>::type foo(T*) {
std::cout << "Bar*\n";
}
void foo(std::nullptr_t) {
std::cout << "nullptr_t\n";
}
int main() {
Bar b;
foo(nullptr);
foo(0);
foo(&b);
}
我想在实践中您的 Bar*
可能会传递派生类型,即您可能需要 std::enable_if
中的不同条件。如果 foo(Bar*)
有点可观,您可能不想在 header 中实施。但是,由于无论如何只有一个实例化,您可以将实现放入 .cpp
文件并显式实例化它,或者分派给实现函数。
在 C++ 中使用文字 0
时,编译器无法区分函数的指针和 nullptr_t
重载。
说明问题的代码:
struct Bar {};
void foo(Bar*) {
std::cout << "Bar*" << std::endl;
}
void foo(std::nullptr_t) {
std::cout << "nullptr_t" << std::endl;
}
TEST(NullPtrTest, ambiguity) {
foo(nullptr); // OK
foo(0); // ERROR
}
与 Visual Studio 2019:
error C2668: '`anonymous-namespace'::foo': ambiguous call to overloaded function
message : could be 'void `anonymous-namespace'::foo(std::nullptr_t)'
message : or 'void `anonymous-namespace'::foo(`anonymous-namespace'::Bar *)'
message : while trying to match the argument list '(int)'
使用 GCC 9:
Test.cpp: In member function ‘virtual void {anonymous}::NullPtrTest_ambiguity_Test::TestBody()’:
Test.cpp:425:8: error: call of overloaded ‘foo(int)’ is ambiguous
425 | foo(0); // ERROR
| ^
Test.cpp:415:6: note: candidate: ‘void {anonymous}::foo({anonymous}::Bar*)’
415 | void foo(Bar*) {
| ^~~
Test.cpp:419:6: note: candidate: ‘void {anonymous}::foo(std::nullptr_t)’
419 | void foo(std::nullptr_t) {
| ^~~
解决这个问题的好方法是什么?
我不想做的事情:
- 将
0
文字替换为nullptr
无处不在。我们遗留代码库中的实例太多了。 - 添加
int
或long
(或类似)重载。由于0
将是唯一有效的整数值,因此您必须添加运行时检查,这会很难看。 - 删除
nullptr_t
重载并在运行时检查指针重载中的值。这行得通,而且并不糟糕,但它阻止了我们对空常量进行优化实现。
谢谢!
澄清:
- 我们正在使用 C++14。但是,这些函数被许多早于 C++11 的代码使用。
- 特定用例是在使用
nullptr
或0
时提供优化的constexpr
实现。通用指针实现仍然需要检查空值。nullptr_t
重载并不是真正必要的,但它会很好。 - 我问这个主要是想看看有没有我没有考虑的选项。
模板不在排除列表中 - 将其设为模板,限制为仅接受 Bar*
:
#include <iostream>
#include <type_traits>
struct Bar {};
template <typename T>
typename std::enable_if<std::is_same_v<T, Bar>>::type foo(T*) {
std::cout << "Bar*\n";
}
void foo(std::nullptr_t) {
std::cout << "nullptr_t\n";
}
int main() {
Bar b;
foo(nullptr);
foo(0);
foo(&b);
}
我想在实践中您的 Bar*
可能会传递派生类型,即您可能需要 std::enable_if
中的不同条件。如果 foo(Bar*)
有点可观,您可能不想在 header 中实施。但是,由于无论如何只有一个实例化,您可以将实现放入 .cpp
文件并显式实例化它,或者分派给实现函数。