Spooky action at a (shorter) distance

327 views
Skip to first unread message

David Krauss

unread,
Jan 22, 2015, 4:26:59 AM1/22/15
to std-dis...@isocpp.org
This program implements a compile-time type-map supporting insertion at local scope together with persistence out to global scope. Clang happily runs it, although GCC 4.9.2 mistakes the friend declaration for a nonstatic member. Is it conforming?

[temp.point] §14.6.4.1 says “the point of instantiation for [a class template] specialization immediately precedes the namespace scope declaration or definition that refers to the specialization,” and [temp.inject] §14.6.5 says “the names of [a class template specialization’s] friends are treated as if the specialization had been explicitly declared at its point of instantiation.”

According to the points of instantiation of the functions that form the type-map, they are all available before the beginning of main. So far, so good. But that also means that the contents of the map could just as well be retrieved before they are injected — going backwards in time, if you like. (Clang doesn’t like that quite as much.)

This is a very useful construct. Perhaps finer sequencing should be defined for points of instantiation, so as to allow it. Otherwise, try and poke a hole in it without bringing down the whole house of cards ;) .

template< typename k >
struct t {
    friend auto leak( t );

    template< typename v >
    struct m {
        friend auto leak( t ) { return v{}; }
    };
};

struct hi { hi() { std::cout << "hi\n"; } };
struct bi { bi() { std::cout << "bi\n"; } };

int main() {
    t< int >::m< hi >{};
    decltype( leak( t< int >{} ) ) {};

    t< char >::m< bi >{};
    decltype( leak( t< char >{} ) ) {};
}

David Krauss

unread,
Jan 22, 2015, 5:43:32 AM1/22/15
to std-dis...@isocpp.org

On 2015–01–22, at 5:26 PM, David Krauss <pot...@gmail.com> wrote:

This is a very useful construct. Perhaps finer sequencing should be defined for points of instantiation, so as to allow it. Otherwise, try and poke a hole in it without bringing down the whole house of cards ;) .

The issue doesn’t seem to be new; here is a C++98 program exposing it. However, this demonstration doesn’t work very well because GCC and Clang generate the instantiation over-eagerly, when it was never used as a complete type. EDG does produce the spooky result.

template< typename t >
char has( t );

template< typename >
struct tag {};

template< int v >
struct t {
    friend long has( tag< t > );
};

int main() {
    std::cout << sizeof has( tag< t< 0 > >() ) << '\n';
    t< 0 > q; // precise point of instantiation
    std::cout << sizeof has( tag< t< 0 > >() ) << '\n';
}

David Krauss

unread,
Jan 22, 2015, 6:02:25 AM1/22/15
to std-dis...@isocpp.org

> On 2015–01–22, at 6:43 PM, David Krauss <pot...@gmail.com> wrote:
>
> The issue doesn’t seem to be new; here is a C++98 program exposing it. However, this demonstration doesn’t work very well because GCC and Clang generate the instantiation over-eagerly, when it was never used as a complete type. EDG does produce the spooky result.

Aha! The instantiation occurs because ADL is “affected by the completeness of the class type,” ([temp.inst] §14.7.1) regardless of whether the class even declares a friend. There’s probably a DR out there which isn’t reflected in the 2012-vintage EDG I’m using online.

Since ADL incurs instantiation of every class associated with a call, and all their hidden friends, C++98 had no way to generate a friend and associate it with a formerly incomplete class. (Furthermore, defining a forward-declared class after it has been associated with any overload resolution violates the ODR because there are conflicting points of instantiation. Good to know… that should probably be diagnosed in practice.)

TL;DR: The defect, if there is one, may indeed be new in C++14.
Message has been deleted

Richard Smith

unread,
Feb 3, 2015, 6:25:29 PM2/3/15
to std-dis...@isocpp.org
(This whole thread was spam-filtered for me for some reason.)

On Thu, Jan 22, 2015 at 3:39 AM, David Krauss <pot...@gmail.com> wrote:
This program implements a compile-time type-map supporting insertion at local scope together with persistence out to global scope. Clang happily runs it, although GCC 4.9.2 mistakes the friend declaration for a nonstatic member. Is it conforming?

[temp.point] §14.6.4.1 says “the point of instantiation for [a class template] specialization immediately precedes the namespace scope declaration or definition that refers to the specialization,” and [temp.inject] §14.6.5 says “the names of [a class template specialization’s] friends are treated as if the specialization had been explicitly declared at its point of instantiation.”

According to the points of instantiation of the functions that form the type-map, they are all available before the beginning of main. So far, so good. But that also means that the contents of the map could just as well be retrieved before they are injected — going backwards in time, if you like. (Clang doesn’t like that quite as much.)

This is a very useful construct. Perhaps finer sequencing should be defined for points of instantiation, so as to allow it. Otherwise, try and poke a hole in it without bringing down the whole house of cards ;)

This is a really interesting technique. It certainly violates my intuition for what should be possible, but I can't see any way in which it violates the current language rules.

The best approach I've got at poking a hole in it is: only the first declaration of 'leak' is visible through ADL, and we cannot deduce the return type for that declaration (name lookup did not find a declaration of 'leak' for which the return type could be deduced).

template< typename k >
struct t {
    friend auto leak( t );

    template< typename v >
    struct m {
        friend auto leak( t ) { return v{}; }
    };
};

struct hi { hi() { std::cout << "hi\n"; } };
struct bi { bi() { std::cout << "bi\n"; } };

int main() {
    t< int >::m< hi >{};
    decltype( leak( t< int >{} ) ) {};

    t< char >::m< bi >{};
    decltype( leak( t< char >{} ) ) {};
}

Note that the issue has existed since C++98. The new part is the leakage mechanism which adds some motivation to actually do this.


--

---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussio...@isocpp.org.
To post to this group, send email to std-dis...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.

Reply all
Reply to author
Forward
0 new messages