C++ type constraint using stream I/O fails if prototype is local

Viewed 75

In the MCVE below, C++ has no trouble identifying that ints may be read or printed with stream I/O. But it thinks As can't be, despite the prototypes for << and >>.

#include <iostream>

using namespace std;

template<typename T>
concept Readable = requires(std::istream& in, T& t)
{
    in >> t;
};

template<typename T>
concept Printable = requires(std::ostream& out, T t)
{
    out << t;
};

int main()
{
    class A {};
    ostream& operator<< (ostream&, const A&);
    istream& operator>> (istream&, A&);

    std::cout << Readable<int> << Printable<int> << '\n';
    std::cout << Readable<A>   << Printable<A>   << '\n';

    return 0;
}

The output is

11
00

If I make the prototypes and the class global, the problem is fixed (it prints 11, not 00, for the 2nd line of output).

Obviously I'd make prototypes global normally. But I don't see how scoping rules let the constraint know where it was defined, as long as it is defined. Why doesn't it work when the prototypes are local?

1 Answers

In both expressions -- in >> t and out << t -- t is a dependent name, which means the name lookup procedure for operator>> and operator<< is deferred until T is known (e.g., the first occurence of Readable<A> and Printable<A>, where T=A).

At this point, the compiler can perform non-ADL lookup (from the context of template definition) and ADL lookup.

For non-ADL lookup, it finds no matching declaration of operator>> and operator<< preceeding the concepts definitions.

For ADL lookup, it considers associated namespaces, in particular, the namespace of A. Class A, however, is locally defined inside function main(), and for that:

[class.local]/p1:

A class can be declared within a function definition; such a class is called a local class. The name of a local class is local to its enclosing scope. The local class is in the scope of the enclosing scope, [...]

Although the name A is local to main(), the class itself lives in the namespace enclosing main() - the global scope.

This means that in ADL, the compiler can look for the operators inside the scope of A but also in the global scope. However, for the two operator declarations the following rule applies:

[basic.link]/p7:

When a block scope declaration of an entity with linkage is not found to refer to some other declaration, then that entity is a member of the innermost enclosing namespace. However such a declaration does not introduce the member name in its namespace scope.

A function declaration by default has external linkage. Hence, neither operator is visible for name lookup, and the concepts are not satisfied.

Related