Search code examples
c++templateslanguage-lawyerinlinefriend

Why can't I inline-define a non-templated friend within a templated class?


MCVE's speak louder than words:

// int bar();
template <bool B> class Foo {
    friend int ::bar() { return 123; }
};

int main()
{
    Foo<false> f1;
    Foo<true> f2;
}

with GCC 6 and --std=c++14, this gives me:

a.cpp: In instantiation of ‘class Foo<true>’:
a.cpp:9:12:   required from here
a.cpp:3:13: error: redefinition of ‘int bar()’
  friend int ::bar() { return 123; }
             ^~
a.cpp:3:13: note: ‘int bar()’ previously defined here

Now, I'm not sure what the standard says; but I know that the compiler knows that the friend is not templated on B, nor does its definition use B. So why can't it apply the "oh, all inline copies of the same definition of a function are the same" rule?


Solution

  • Now, I'm not sure what the standard says;

    This case has in fact been clarified with an example in the upcoming C++17

    [temp.inst]/2 The implicit instantiation of a class template specialization ... [snip] ... for the purpose of determining whether an instantiated redeclaration of a member is valid according to 3.2 [basic.def.odr] and 9.2 [class.mem], a declaration that corresponds to a definition in the template is considered to be a definition. [ Example:

    ... [snip (another example)] ...

    template<typename T> struct Friendly {
      template<typename U> friend int f(U) { return sizeof(T); }
    };
    Friendly<char> fc;
    Friendly<float> ff; // ill-formed: produces second definition of f(U)
    

    — end example  ]

    Admittedly as you point out, the example of the standard does produce a different definition for each instantiation, but that is not necessary for the example to be ill-formed according to that rule.

    So why can't it apply the "oh, all inline copies of the same definition of a function are the same" rule?

    This question seems to apply to the a much simpler situation as well:

    inline void foo(){}
    inline void foo(){}
    

    Surely a compiler can see that the definitions are identical, just as much as a compiler can see that the definition of your ::bar depends not on the template argument of Foo.

    Yet, odr says that the re-definition is ill-formed. This is true for definitions outside a class template, as well as definitions that are caused by instantiation of a class template.


    Perhaps odr could be relaxed for the case that you demonstrate, but that would require complicating the standard with a special case rule, and complicate the compilers that then would have to analyse whether template arguments are used within the definition, so such relaxation certainly isn't without compromise.