为什么在NVCC中,定义隐藏友元二元运算符operator- 与operator== 的顺序,以及使用int与 concepts的区别,会对NVCC产生影响?

编程语言 2026-07-10

我正在为CUDA设备指针创建一个类似指针的类型包装器,但在常规的MSVC或 GCC编译器中遇到了一个问题,无法在其他编译器上重现。基本上,取决于我定义运算符- 的顺序,我把它定义为一个隐藏的友元函数,如下所示:

    [[nodiscard]]
    friend TestPointerLike operator-(TestPointerLike lhs, std::integral auto n) noexcept  {
        //    lhs -= n; removed for now for simplicity in example, since only testing compilation
        return lhs;
    }

根据是在 operator == 之前用 std::nullopt_t 定义,还是之后,或者如果我使用 int,或者为 n 使用 std::integral auto,并且我把它设为非隐藏友元并仅在类定义之外定义它,它可能会编译也可能不会编译 == 运算符。为什么会这样?

以下是最小示例(总共有四个测试,最后一个测试 TestPointerLike4 是失败的情况)。

Godbolt链接也在这里(使用NVCC):https://godbolt.org/z/cWsP3zoEj Godbolt链接到它在MSVC和 GCC中工作的版本:https://godbolt.org/z/a7EKr5hb9

#include <type_traits> 
#include <concepts>
#include <iostream>  


template<typename T>
struct TestPointerLike {
private:
    T *m_data = nullptr;
public:

    [[nodiscard]]
    T *get() noexcept {
        return m_data;
    }

    [[nodiscard]]
    const T *get() const {
        return m_data;
    }

    TestPointerLike() noexcept = default;


    explicit  constexpr TestPointerLike(std::nullptr_t) noexcept : TestPointerLike() {};



    [[nodiscard]]
     friend   bool operator==(TestPointerLike dev_ptr, std::nullptr_t) noexcept{
        return dev_ptr.get() == nullptr;
    }

    [[nodiscard]]
    friend    bool operator!=(TestPointerLike dev_ptr, std::nullptr_t) noexcept{
        return dev_ptr.get() != nullptr;
    }

    [[nodiscard]]
    friend TestPointerLike operator-(TestPointerLike lhs, std::integral auto n) noexcept {
        //    lhs -= n;
        return lhs;
    }

};

template<typename T>
struct TestPointerLike2 {
    private:
        T *m_data = nullptr;
    public:

    [[nodiscard]]
    T *get() noexcept {
        return m_data;
    }

    [[nodiscard]]
    const T *get() const {
        return m_data;
    }

    TestPointerLike2() noexcept = default;


    explicit  constexpr TestPointerLike2(std::nullptr_t) noexcept : TestPointerLike2() {};

    [[nodiscard]]
    friend TestPointerLike2 operator-(TestPointerLike2 lhs, int n) noexcept  {
    //    lhs -= n;
       return lhs;
    }

    [[nodiscard]]
     friend   bool operator==(TestPointerLike2 dev_ptr, std::nullptr_t) noexcept{
        return dev_ptr.get() == nullptr;
    }

    [[nodiscard]]
    friend    bool operator!=(TestPointerLike2 dev_ptr, std::nullptr_t) noexcept{
        return dev_ptr.get() != nullptr;
    }

};



template<typename T>
struct TestPointerLike3 {
    private:
        T *m_data = nullptr;
    public:

    [[nodiscard]]
    T *get() noexcept {
        return m_data;
    }

    [[nodiscard]]
    const T *get() const {
        return m_data;
    }

    TestPointerLike3() noexcept = default;


    explicit  constexpr TestPointerLike3(std::nullptr_t) noexcept : TestPointerLike3() {};


    friend TestPointerLike3 operator-(TestPointerLike3 lhs, std::integral auto n) noexcept;

    [[nodiscard]]
     friend   bool operator==(TestPointerLike3 dev_ptr, std::nullptr_t) noexcept{
        return dev_ptr.get() == nullptr;
    }

    [[nodiscard]]
    friend    bool operator!=(TestPointerLike3 dev_ptr, std::nullptr_t) noexcept{
        return dev_ptr.get() != nullptr;
    }

};

template<typename T> 
[[nodiscard]]
TestPointerLike3<T> operator-(TestPointerLike3<T> lhs, std::integral auto n) noexcept  {
//    lhs -= n;
    return lhs;
}



template<typename T>
struct TestPointerLike4 {
    private:
        T *m_data = nullptr;
    public:

    [[nodiscard]]
    T *get() noexcept {
        return m_data;
    }

    [[nodiscard]]
    const T *get() const {
        return m_data;
    }

    TestPointerLike4() noexcept = default;


    explicit  constexpr TestPointerLike4(std::nullptr_t) noexcept : TestPointerLike4() {};

    [[nodiscard]]
    friend TestPointerLike4 operator-(TestPointerLike4 lhs, std::integral auto n) noexcept  {
    //    lhs -= n;
       return lhs;
    }

    [[nodiscard]]
     friend   bool operator==(TestPointerLike4 dev_ptr, std::nullptr_t) noexcept{
        return dev_ptr.get() == nullptr;
    }

    [[nodiscard]]
    friend    bool operator!=(TestPointerLike4 dev_ptr, std::nullptr_t) noexcept{
        return dev_ptr.get() != nullptr;
    }

};

int main() {
    {
        TestPointerLike<double> f64_ptr; 
        bool bool_test = f64_ptr == nullptr;
    }
    {
        TestPointerLike2<double> f64_ptr;
        bool bool_test = f64_ptr == nullptr;
    }
    {
        TestPointerLike3<double> f64_ptr;
        bool bool_test = f64_ptr == nullptr;
    }
    {
        TestPointerLike4<double> f64_ptr;
        bool bool_test = f64_ptr == nullptr;
    }
}

我得到的错误是:

<source>(187): error: no operator "==" matches these operands
            operand types are: TestPointerLike4<double> == std::nullptr_t
          bool bool_test = f64_ptr == nullptr;
                                   ^
<source>(187): note #3328-D: built-in operator==(<promoted arithmetic>, <promoted arithmetic>) does not match because argument #1 does not match parameter
          bool bool_test = f64_ptr == nullptr;
                                   ^
<source>(187): note #3328-D: built-in operator==(<nullptr>, <nullptr>) does not match because argument #1 does not match parameter
          bool bool_test = f64_ptr == nullptr;
                                   ^

1 error detected in the compilation of "<source>".
Compiler returned: 2

解决方案

In TestPointerLike4, the compiler may fail to resolve operator== because the hidden operator- declaration interferes with ADL or overload resolution when std::nullopt_t is involved. NVCC may not consider the operator== overload as viable if the operator- is defined in a way that confuses the lookup order or template deduction.

Try to replace std::nullopt_t with std::nullptr_t or a plain int temporarily to isolate the issue.

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章