C++20 out-of-class definition in a template class

ProXicT :

Up until C++20 standard of C++, when we wanted to define an out-of-class operator which uses some private members of a template class, we'd use a construct similar to this:

template <typename T>
class Foo;

template <typename T>
constexpr bool operator==(T lhs, const Foo<T>& rhs);

template <typename T>
class Foo {
public:
    constexpr Foo(T k) : mK(k) {}

    constexpr friend bool operator==<T>(T lhs, const Foo& rhs);

private:
    T mK;
};

template <typename T>
constexpr bool operator==(T lhs, const Foo<T>& rhs) {
    return lhs == rhs.mK;
}

int main() {
    return 1 == Foo<int>(1) ? 0 : 1;
}

Since C++20, however, we can omit the out-of-class declaration, thus also the forward declaration, so we can get away with just:

template <typename T>
class Foo {
public:
    constexpr Foo(T k) : mK(k) {}

    constexpr friend bool operator==<T>(T lhs, const Foo& rhs);
private:
    T mK;
};

template <typename T>
constexpr bool operator==(T lhs, const Foo<T>& rhs) {
    return lhs == rhs.mK;
}

Demo

Now, my question is, what part of C++20 allows us to do so? And why wasn't this possible in earlier C++ standards?


As it was pointed out in the comments, clang doesn't accept this code presented in the demo, which suggests this might actually be a bug in gcc.

I filed a bug report on gcc's bugzilla

Richard Smith :

GCC has a bug.

Name lookup is always performed for template names appearing before a <, even when the name in question is the name being declared in a (friend, explicit specialization, or explicit instantiation) declaration.

Because the name operator== in the friend declaration is an unqualified name and is subject to name lookup in a template, the two-phase name lookup rules apply. In this context, operator== is not a dependent name (it's not part of a function call, so ADL does not apply), so the name is looked up and bound at the point where it appears (see [temp.nondep] paragraph 1). Your example is ill-formed because this name lookup finds no declaration of operator==.

I would expect GCC is accepting this in C++20 mode due to P0846R0, which permits (for example) operator==<T>(a, b) to be used in a template even if no prior declaration of operator== as a template is visible.

Here's an even more interesting testcase:

template <typename T> struct Foo;

#ifdef WRONG_DECL
template <typename T> bool operator==(Foo<T> lhs, int); // #1
#endif

template <typename T> struct Foo {
  friend bool operator==<T>(Foo<T> lhs, float); // #2
};

template <typename T> bool operator==(Foo<T> lhs, float); // #3
Foo<int> f;

With -DWRONG_DECL, GCC and Clang agree that this program is ill-formed: unqualified lookup for the friend declaration #2, in the context of the template definition, finds the declaration #1, which doesn't match the instantiated friend of Foo<int>. Declaration #3 is not even considered, because unqualified lookup in the template doesn't find it.

With -UWRONG_DECL, GCC (in C++17 and earlier) and Clang agree that this program is ill-formed for a different reason: unqualified lookup for operator== on line #2 finds nothing.

But with -UWRONG_DECL, GCC in C++20 mode appears to decide that it's OK that unqualified lookup for operator== in #2 fails (presumably due to P0846R0), and then appears to redo the lookup from the template instantiation context, now finding #3, in violation of the normal two-phase name lookup rule for templates.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=29425&siteId=1