Search code examples
c++templateslanguage-lawyerone-definition-rule

Can ODR be violated if a template definition is only ever instantiated with different parameters?


I'm trying to figure out whether the following test program is well-formed. It's not clear to me whether the definition of function template my_min violates ODR or not. The program consists of 3 translation units:

// unit1.cpp

template <typename T>
T my_min(T a, T b)
{
    return my_less(a, b) ? a : b;
}

struct Test1 {};

inline bool my_less(Test1, Test1)
{
    return true;
}

// Explicit instantiation. Only in this translation unit.
template
Test1 my_min<Test1>(Test1, Test1);
// unit2.cpp

template <typename T>
T my_min(T a, T b)
{
    return my_less(a, b) ? a : b;
}

struct Test2 {};

inline bool my_less(Test2, Test2)
{
    return false;
}

// Explicit instantiation. Only in this translation unit.
template
Test2 my_min<Test2>(Test2, Test2);
// test.cpp, and maybe many other translation units

template <typename T>
T my_min(T a, T b);

// There's no definition of my_min<T>(T, T) in this translation unit.
// That should be ok as long as some other translation unit provides explicit instantiation.

struct Test1 {};
struct Test2 {};

void test()
{
    Test1     t1;
    Test2     t2;

    my_min(t1, t1);
    my_min(t2, t2);
}

Though the definition of my_min consists of exactly the same sequence of tokens in both unit1.cpp and unit2.cpp, it contains dependent expression my_less(a, b).

The standard (the current draft) states that:

If D is a template and is defined in more than one translation unit, the requirements apply both to names from the template's enclosing scope used in the template definition, and also to dependent names at the point of instantiation

Requirements mentioned here are quite numerous, and one of them is giving me troubles:

In each such definition, corresponding names, looked up according to [basic.lookup], shall refer to the same entity, after overload resolution ([over.match]) and after matching of partial template specialization

Obviously, dependent name my_less, when looked up at the point of instantiation, refers to different entities: my_less(Test1, Test1) and my_less(Test2, Test2). Which, I dare say, is quite expected since it's dependent.

So the question is: is this a violation of ODR requirements quoted above (or any other requirements, for that matter)?

I have a gut feeling that this should not be a violation, since a similar scenario pops up every time some translation unit defines a private class (like Test1 and Test2 above) along with functions like swap() and begin() for that class, and then instantiates some standard algorithm for that class which would find those functions via ADL at the point of instantiation. That scenario is quite common... It can't be wrong, right?


A link to godbolt example: link.


Solution

  • I think the intention is that "and also to dependent names at the point of instantiation" applies only when the ODR is considered at the point of instantiation per [temp.point]/7, which considers every specialization of the template individually with regards to its points of instantiation.

    I would expect "looked up according to [basic.lookup]" to imply that the ODR also considers only lookup that is actually performed in the context of the definition (or the context of the instantiation if considering [temp.point]/7).

    I don't see anything else making sense. So then your program would have well-defined behavior.