4
votes

The following code compiles without any error, though it seems to break ODR:

#include <iostream>

template<long Num>
class B;

template<long Num>
struct A {
    template<long Num1>
    friend void ffoo(A<Num1> a, B<Num>* = nullptr) {
        std::cout << "@A ffoo(A<" << Num1 << ">, B<" << Num << ">*)" << std::endl;
    }
};

template<long Num>
class B {
public:
    friend void ffoo(A<Num> a, B<Num>* = nullptr) {
        std::cout << "@B ffoo(A<" << Num << ">, B<" << Num << ">*)" << std::endl;
    }
};

int main() {
    ffoo(A<1>{});         // @A ffoo(A<1>, B<1>*)
    B<1>* ptr = nullptr;
    ffoo(A<1>{}, ptr);    // @B ffoo(A<1>, B<1>*)
}

The ODR Rules allow cases for which breaking of ODR is IFNDR (ill formed no diagnostic required) all these cases seem to relate to programs with multiple translation units.

The first paragraph is quite clearly stating the requirement from a single translation unit:

[basic.def.odr]/1

No translation unit shall contain more than one definition of any variable, function, class type, enumeration type, template, default argument for a parameter (for a function in a given scope), or default template argument.

Does the above code break ODR? If it does, should breaking ODR inside a single translation unit require compiler diagnostics?


* Note: it seems that the friend template function in the code example do obey to the new rules of [temp.inst].

1
Could you explain why you think this code violates ODR? I'm not seeing multiple definitions.NathanOliver
@NathanOliver there are two definitions of ffoo(A<1>, B<1>*) isn't it?Amir Kirsh
The friend of B is not a function template. Those are overloadsStoryTeller - Unslander Monica
templates have special rules. If a regular function is the same as a template, the regular function is chosen, no ODR violation.NathanOliver
You are correct that a redefinition is diagnosable. It is indeed diagnosed when you actually redefine ffoo coliru.stacked-crooked.com/a/954c266e2aae7645StoryTeller - Unslander Monica

1 Answers

5
votes

The friend of B is not a function template. That friend declaration is not a template declaration, so we have

[temp.friend] (emphasis mine)

1 A friend of a class or class template can be a function template or class template, a specialization of a function template or class template, or a non-template function or class. For a friend function declaration that is not a template declaration:

  • if the name of the friend is a qualified or unqualified template-id, the friend declaration refers to a specialization of a function template, otherwise,

  • if the name of the friend is a qualified-id and a matching non-template function is found in the specified class or namespace, the friend declaration refers to that function, otherwise,

  • if the name of the friend is a qualified-id and a matching function template is found in the specified class or namespace, the friend declaration refers to the deduced specialization of that function template ([temp.deduct.decl]), otherwise,

  • the name shall be an unqualified-id that declares (or redeclares) a non-template function.

So the two declarations of ffoo don't declare the same entity. One is a function template, the other is a non-template function. These two may exist in the same declarative region as overloads.

So there is no ODR violation here.