1

Consider the following class:

template<class a>
class c
{
public:
    int x = 0;
    // not really templated
    friend constexpr auto operator +( int const left,
                                      c const & a ) noexcept
    {
        return c{ left + x };
    }
};

How does one move the function body without declaring it in the outer scope? e.g. pseudo:

template<class a>
friend constexpr auto c<a>::operator +( int const left,
                                        c const & a ) noexcept
{
    return c{ left + x };
}

2 Answers2

1

The first snippet of OP:

class c
{
public:
    int x = 0;
    friend constexpr auto operator +( int const left,
                                      c const & a ) noexcept
    {
        return c{ left + x };
    }
};

It shows a free function (operator) that is inline defined in class c as friend. This is not a member function of class c although it may look so on the first glance. (If it would be a member function it would be a ternary operator+ but there doesn't exist one in C++ nor can it be overloaded.)

The second snippet of OP:

friend constexpr auto c::operator +( int const left,
                                     c const & a ) noexcept
{
    return c{ left + x };
}

It is wrong for two reasons:

  1. friend doesn't make sense. (friend to what?)

  2. If it's a free function - scope c:: doesn't make sense.

The friendship of functions cannot be declared outside of the resp. class.

If this would be allowed everybody could declare friendship of functions everywhere which accesses the private members of classes. This would make the sense of private members somehow useless.

(Imagine a safe with an electronic lock and a key pad where the password is written on the door.)

A somehow useless safe

(Original image by Binarysequence - Own work, CC BY-SA 4.0, Link)

Nevertheless, friend functions (and operators) can be defined non-inline, of course.

The following sample shows how:

#include <iostream>

// forward declaration of class c
class c;

// forward declaration of operator +
constexpr auto operator+(int, c const&) noexcept;

// the class c
class c {
  // make operator + a friend to grant access to private members
  friend constexpr auto operator+(int, c const&) noexcept;
  
  private:
    int x;
  
  public:
    c() = default;
    constexpr c(int x): x(x) { }
    
    int get() const { return x; }
};

// the operator +
constexpr auto operator+(int lhs, c const &rhs) noexcept
{
  return c(lhs + rhs.x);
}

// show it in action:

#define DEBUG(...) std::cout << #__VA_ARGS__ << ";\n"; __VA_ARGS__ 

int main()
{
  DEBUG(c a(123));
  DEBUG(std::cout << (234 + a).get() << '\n');
}

Output:

c a(123);
std::cout << (234 + a).get() << '\n';
357

Live Demo on coliru


This is similar when applied to templates (although the syntax is a bit more tricky):

#include <iostream>

// forward declaration of class c
template <typename T>
class c;

// forward declaration of operator +
template <typename T>
constexpr auto operator+(int, c<T> const&) noexcept;

// the class c
template <typename T>
class c {
  friend constexpr auto operator+<>(int, c<T> const&) noexcept;
  
  private:
    T x;
  
  public:
    c() = default;
    constexpr c(T x): x(x) { }
    
    int get() const { return x; }
};

// the operator +
template <typename T>
constexpr auto operator+(int lhs, c<T> const &rhs) noexcept
{
  return c(lhs + rhs.x);
}

// show it in action:

#define DEBUG(...) std::cout << #__VA_ARGS__ << ";\n"; __VA_ARGS__ 

int main()
{
  DEBUG(c<int> a(123));
  DEBUG(std::cout << (234 + a).get() << '\n');
}

Output:

c<int> a(123);
std::cout << (234 + a).get() << '\n';
357

Live Demo on coliru


More about inline friend functions:

Scheff's Cat
  • 19,528
  • 6
  • 28
  • 56
  • This is a good answer explaining how friend operators work but inlining a friend function is different from declaring it in the outer scope. I'll edit my question to show why and what problem I'm trying to solve. –  Jul 17 '20 at 08:43
  • @superdeveloper I just recalled [cppreference.com - Template friend operators](https://en.cppreference.com/w/cpp/language/friend#Template_friend_operators). I'm afraid what you want is not allowed nor possible. – Scheff's Cat Jul 17 '20 at 08:49
  • 1
    @superdeveloper I made a modified [**Live Demo on coliru**](http://coliru.stacked-crooked.com/a/a4239ad36460e2fd) but I'm not quite sure whether this is what you are looking for. I remember that I once took part in a Q/A about making a function template a friend of a class whereby only the corresponding template instances with equal type should have friendship. However, I cannot find it anymore... :-( – Scheff's Cat Jul 17 '20 at 09:01
  • I see; so the secret is to make it `operator +<>`. This is great information. –  Jul 17 '20 at 09:06
1

The closest you can get is to define the function in a source file such that its out-of-class declaration is not visible to (other) clients. (You would not be able to have its return type be deduced in that case.) In C++20, you can do something very similar by not exporting the friend from a module.

It has been suggested that what you want should be achievable with a differently qualified definition:

constexpr auto ::operator +( int const left,
                             c const & a ) noexcept
{
    return c{ left + x };
}

The otherwise redundant scope resolution operator would cause the definition to “not count” for name lookup, but GCC and Clang currently give it other interpretations: Clang does let ordinary lookup find the function (and issues a warning in the global scope case), while GCC rejects it entirely.

Davis Herring
  • 36,443
  • 4
  • 48
  • 76