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?

anatolyg
  • 26,506
  • 9
  • 60
  • 134
einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • 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? – anatolyg Sep 24 '17 at 23:21
  • 2
    Why not just define it outside the class template? – Barry Sep 24 '17 at 23:28
  • @Barry: It's convenient for me because I have some related definitions closeby within the class. – einpoklum Sep 24 '17 at 23:35
  • @anatolyg: It's implicitly `inline` since it's in the class definiton (IIANM); adding `inline` doesn't help. – einpoklum Sep 24 '17 at 23:36
  • 5
    "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() {}`? –  Sep 24 '17 at 23:44
  • @hvd you should make that into an answer – Brian Bi Sep 24 '17 at 23:50
  • @anatolyg according to [class.friend]/7, friend functions defined in a class are implicitly inline – M.M Sep 25 '17 at 00:06
  • @hvd: "Why would it?" Because that would allow you to define non-templated friends within a templated class, which right now you can't. – einpoklum Sep 25 '17 at 00:11

1 Answers1

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.

Community
  • 1
  • 1
eerorika
  • 232,697
  • 12
  • 197
  • 326
  • 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. – einpoklum Sep 25 '17 at 00:09
  • @einpoklum I see your point about the example. It changes nothing as far as the rule is concerned, however. – eerorika Sep 25 '17 at 00:10
  • @Barry 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) – M.M Sep 25 '17 at 00:12
  • @einpoklum No, it's not. The fact that the friend function definition uses `T` doesn't matter for the rule. – Barry Sep 25 '17 at 01:17
  • The last paragraph is the actual answer AFAIAC. – einpoklum Sep 25 '17 at 06:51
  • ... but I don't like the answer :-( – einpoklum Sep 25 '17 at 10:20