Why does order of defining hidden friend binary operator- vs operator==, and using int vs concepts matter to NVCC?
01:00 31 Mar 2026

I'm trying to create a pointer like type wrapper for CUDA device pointers, but ran into a problem I can't reproduce with other compilers in regular MSVC or GCC. Basically, depending on the order I define operator- which I define as a hidden friend like so:

    [[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;
    }

Depending on if I define it before operator == with std::nullopt_t, or after, or if I use int, or std::integral auto for n and I make it a non-hidden friend and just define it outside the class definition it may or may not compile the == operator. Why is this the case?

The following is the minimal example (there are four tests, with the final one, TestPointerLike4, being the failing case).

Godbolt link also here (using NVCC): https://godbolt.org/z/cWsP3zoEj Godbolt link to it working in MSVC and GCC https://godbolt.org/z/a7EKr5hb9

#include  
#include 
#include   


template
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
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
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 
[[nodiscard]]
TestPointerLike3 operator-(TestPointerLike3 lhs, std::integral auto n) noexcept  {
//    lhs -= n;
    return lhs;
}



template
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 f64_ptr; 
        bool bool_test = f64_ptr == nullptr;
    }
    {
        TestPointerLike2 f64_ptr;
        bool bool_test = f64_ptr == nullptr;
    }
    {
        TestPointerLike3 f64_ptr;
        bool bool_test = f64_ptr == nullptr;
    }
    {
        TestPointerLike4 f64_ptr;
        bool bool_test = f64_ptr == nullptr;
    }
}

the error I get is:


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

1 error detected in the compilation of "".
Compiler returned: 2
c++ cuda c++20 c++-concepts nvcc