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

1

7

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?

Christmann answered 24/9, 2017 at 23:14 Comment(8)
Not sure what the standard says, but it seems the definition here is not inline. Try adding inline to it, and see if it works?Anabranch
Why not just define it outside the class template?Catheryncatheter
@Barry: It's convenient for me because I have some related definitions closeby within the class.Christmann
@anatolyg: It's implicitly inline since it's in the class definiton (IIANM); adding inline doesn't help.Christmann
"So why can't it apply the "oh, all inline copies of the same definition of a function are the same"?" -- Even if it could, why would it? Does the standard allow duplicate definitions of the same function in the same translation unit in any other case? What's so special about this case that it should be treated differently from e.g. inline void f() {} inline void f() {}?Clemenciaclemency
@hvd you should make that into an answerWarplane
@Anabranch according to [class.friend]/7, friend functions defined in a class are implicitly inlineNauru
@hvd: "Why would it?" Because that would allow you to define non-templated friends within a templated class, which right now you can't.Christmann
E
5

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.

Event answered 25/9, 2017 at 0:3 Comment(6)
I'm sorry, but this example is the opposite of what I'm talking about. In your example, the friend is templated on T. In my example, it isn't. If you want to make sure that's the case, you can uncomment my first line, but it really shouldn't matter.Christmann
@Christmann I see your point about the example. It changes nothing as far as the rule is concerned, however.Event
@Catheryncatheter perhaps a bug should be filed against gcc for failing to diagnose that error (and if the OP problem cannot be reproduced without the :: then it would also be an answer to the question)Nauru
@Christmann No, it's not. The fact that the friend function definition uses T doesn't matter for the rule.Catheryncatheter
The last paragraph is the actual answer AFAIAC.Christmann
... but I don't like the answer :-(Christmann

© 2022 - 2024 — McMap. All rights reserved.