1

Why does taking the address of an inline friend not result in code being produced. This works for regular inline functions. Is the declaration not matching the inline friend somehow?

#include <iostream>

namespace S
{

template <unsigned N>
class X
{
    int i;

    friend X operator+(const X& a, const X& b) noexcept
    {
        (void)a;
        (void)b;
        return {};
    }
};

inline X<256> operator+(const X<256>&, const X<256>&) noexcept;

}

int main()
{
    S::X<256> (*ptr)(const S::X<256>& a, const S::X<256>& b) noexcept = &::S::operator+;
    std::cout << (void*)ptr << std::endl;

    return 0;
}
$ g++ --version
g++ (GCC) 11.2.1 20220401 (Red Hat 11.2.1-10)
$ g++ -std=c++17 test2.cpp 
test2.cpp:18:13: warning: inline function ‘S::X<N> S::operator+(const S::X<N>&, const S::X<N>&) [with unsigned int N = 256]’ used but never defined
   18 | inline X<N> operator+(const X<N>&, const X<N>&) noexcept;
      |             ^~~~~~~~
/usr/bin/ld: /tmp/ccoA1Nxj.o: in function `main':
test2.cpp:(.text+0xc): undefined reference to `S::X<256u> S::operator+<256u>(S::X<256u> const&, S::X<256u> const&)'
collect2: error: ld returned 1 exit status
  • 3
    Related/duplicate: [friend and template in C++](https://stackoverflow.com/questions/48626437/friend-and-template-in-c) – Yksisarvinen May 04 '22 at 17:58
  • @Yksisarvinen The problem still happens if you fix that: https://gcc.godbolt.org/z/Mrb3WzMv7 – HolyBlackCat May 04 '22 at 18:02
  • @HolyBlackCat I think the intented fix is to forward declare the operator? https://gcc.godbolt.org/z/nETsqYs6Y – Yksisarvinen May 04 '22 at 18:06
  • 1
    @Yksisarvinen That's one way to fix it, but I'm unsure why my way doesn't work. Your link basically says "friend definition in a template creates a non-template function". Then my non-template function declaration should work (and it does work if you uncomment the call, then the function does get emitted. – HolyBlackCat May 04 '22 at 18:10
  • @HolyBlackCat Yeah, I don't understand the first snippet either. I can't find any other dupe for now, so I though I'd at least link this one here. – Yksisarvinen May 04 '22 at 18:15
  • OP, consider fixing the code in the question to remove the unrelated problems (the declaration outside of the class shouldn't be a template, `X` isn't an aggregate, `a.b` being a typo). – HolyBlackCat May 04 '22 at 18:17
  • @HolyBlackCat It still fails, because nothing in the shown code actually instantiates `S::X<256>` and therefore the definition of the function. – user17732522 May 04 '22 at 18:44
  • @user17732522 taking the address of inline function results in code being generated for that function in my previous experience – blahblahblah3x3 May 04 '22 at 18:47
  • @Yksisarvinen solution of forward declare works, but looking for something less verbose - as in https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Making_New_Friends – blahblahblah3x3 May 04 '22 at 18:48
  • @user17732522 Yep. I wonder why mentioning `S::X<256>` in the type doesn't do it though. – HolyBlackCat May 04 '22 at 18:48
  • @blahblahblah3x3 *"taking the address of inline function results in code being generated"* This is not viable, since multiple classes could generate the same function, and the compiler can't know which one to pick. – HolyBlackCat May 04 '22 at 18:49
  • @HolyBlackCat Using a type as parameter or return type in a function _declaration_ doesn't require it to be complete (it does only in a function definition or potentially during overload resolution). Neither does overload resolution for taking the address require instantiating the class. So there is no cause for an implicit instantiation. – user17732522 May 04 '22 at 18:51
  • @blahblahblah3x3 Yes, taking the address will odr-use the function and therefore will cause implicit instantiation of a function definition if one is available. However, since the class specialization has not been instantiated, the compiler doesn't have a definition available when taking the address. – user17732522 May 04 '22 at 18:53
  • @user17732522 that makes sense, thanks! indeed, adding a variable to `main` of type `X<256>` works also. – blahblahblah3x3 May 04 '22 at 18:58

1 Answers1

0

There is no argument-dependent lookup when taking the address of an overload set.

So, the initialization of the function pointer doesn't require the compiler to instantiate X<256> to figure out whether there is a operator+ in it. For an unqualified function call this would happen.

Neither does the declaration of operator+ outside the class require any of the involved types to be complete. So there is no implicit instantiation of X<256> there either.

Consequently, when the address of the function is taken, X<256> has not been instantiated and so the compiler can't be aware of the friend definition of the function, which is then also not instantiated.

In the end, there is no definition for the function, which however was ODR-used by taking its address and therefore the program is ill-formed, no diagnostic required.

This can be fixed by having X<256> be instantiated before taking the address, e.g. by writing static_assert(sizeof(S::X<256>)); before main to cause implicit instantiation or template S::X<256>; to cause explicit instantiation.

user17732522
  • 53,019
  • 2
  • 56
  • 105