9

I got the errors compiling this code with g++ 4.6 and 4.8. g++ 4.2 and 4.4 is OK. Is it a bug or some new language feature?

template <typename T>
struct A { typedef typename T::value_type type; };

template <typename U>
struct B
{
  void bar () { }
  void foo ()
  {
    // OK
    this->bar ();

    // OK
    (*this).bar ();

    // Error in g++ 4.6-4.8 
    // leads to full instantiating of template arg "U"
    (&*this)->bar ();
  }
};

int main ()
{
  B< A<void> > b;
  b.foo ();
  return 0;
}

g++ inst.cc

inst.cc: In instantiation of ‘struct A<void>’:
inst.cc:20:5:   required from ‘void B<U>::foo() [with U = A<void>]’
inst.cc:27:10:   required from here
inst.cc:3:34: error: ‘void’ is not a class, struct, or union type
   typedef typename T::value_type type;
                                  ^

Update 1: A cannot be instantiated, I know.

The question is: why the compiler tries to instantiate it at "(&*this)->bar ()" line, but not at "this->bar ()" or "(*this).bar ()" lines?

Update 2:

The suggested workaround with addressof (object) is not working for me, because actually I got the error when I tried to use std::bind (&B::bar, this). The real code is much more complex of course and the bind was not used standalone, but the problem was traced to the simple std::bind expression.

I did not want to rewrite or reinvent std::bind, so I had to use CRTP to make it work:

#include <tr1/functional>
template <typename T>
struct A { typedef typename T::value_type type; };

template <typename Derived, typename U>
struct B
{
  Derived* derived (void) { return static_cast<Derived*>(this); }

  void bar () { }
  void foo ()
  {
    // error with recent compiler.
    // std::tr1::bind (&B::bar, this) ();

    // now ok
    std::tr1::bind (&Derived::bar, derived ()) ();
  }
};

struct C: B<C, A<void> >
{
};

int main ()
{
  C c;
  c.foo ();
  return 0;
}

I find such errors and workarounds to be completely illogical though.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Nikki Chumakov
  • 1,215
  • 8
  • 18
  • Well, what I find surprising is that it actually compiles when you do not. I would have thought that by instantiating the template (declaring `B< A >`) you would require `A` to be meaningful. – Matthieu M. Oct 16 '12 at 18:15
  • My question is actually: why this->bar () works, (*this).bar () works, and (&*this)->bar () gives error? – Nikki Chumakov Oct 16 '12 at 18:37
  • To my mind, template parameter U [=A] is not used anywhere in B class and thus it should not be instantiated at all (lazy template instantiation). However it is not a case when one gets the address of B > object. It looks like g++ compiler bug here, is not it? – Nikki Chumakov Oct 16 '12 at 18:42
  • I don't know why this happens, yet, but recent versions of gcc, clang, and EDG all agree on behavior, i.e., I would suspect that the standard mandates it. – Dietmar Kühl Oct 16 '12 at 18:49
  • @NikkiChumakov Did you catch my updates in [the comments](http://stackoverflow.com/questions/12920808/getting-the-address-of-template-class-object-leads-to-full-instatiation-of-templ/12921672#comment17580455_12921672)? I'd love to hear whether you solved the problem – sehe Oct 19 '12 at 08:46

1 Answers1

11

Analysis/explanation:

What you are seeing is shallow instantiation, not full (see below for proof).

ADL is the culprit here.

Hypothesis II I'm suspecting an ADL-related thing here (classes can have static free functions (friends) declared inline. Perhaps the compiler needs to instantiate the whole class template in order to make sure it has seen the operator overloads declared in it (in order to do overload resolution).

The standard backs me up here: §3.4.2 (p46 in n3337):

² [snip] The sets of namespaces and classes is determined entirely by the types of the function arguments (and the namespace of any template template argument). [snip] The sets of namespaces and classes are determined in the following way:

  • [snip]

  • If T is a class type (including unions), its associated classes are: the class itself; the class of which it is a member, if any; and its direct and indirect base classes. Its associated namespaces are the namespaces of which its associated classes are members. Furthermore, if T is a class template specialization, its associated namespaces and classes also include: the namespaces and classes associated with the types of the template arguments provided for template type parameters (excluding template template parameters); the namespaces of which any template template arguments are members; and the classes of which any member templates used as template template arguments are members.

The bolded phrase includes class A<void> as a lookup namespace for ADL.

Workaround:

In your situation std::addressof(b) can be used instead of &b and it will work.

Demonstration:

See http://liveworkspace.org/code/4f85a06598eebe1d8060112be36f4a29

Note: the (unqualified-id) trick is defined in §3.4.2 of the standard)

#include <vector>
#include <iostream>

struct Base {};

template <typename U> struct B : Base { };

template <typename T> struct A {
    typedef typename T::value_type type;
    friend void freefunction(B<A>&) { std::cout << "ADL was here!\n"; }
};

void freefunction(Base& /*acceptAll*/) {}

int main ()
{
    B< A<std::vector<int> > >  a;
    B< A<void> >               b;

    // surrounding with parens prevents ADL:
    (freefunction)(a);
    (freefunction)(b); // selects ::freefunction(Base&)

    freefunction(a);   // ADL selects friend inline freefunction(B< A<std::vector<int> > >&)
  //freefunction(b);   // ADL fails: template arg cannot be (shallow) instantiated
}

Prints

ADL was here!

Also, you can verify that the template argument (A<void>) gets shallow instantiated only. Moving the ill-formed typedef into a member function removes the problem:

template <typename T> struct A {
    void uninstantiated() {
        typedef typename T::value_type type;
    }
    friend void freefunction(B<A>&) { std::cout << "ADL was here!\n"; }
};

Outputs (http://liveworkspace.org/code/a15c933293281d0926e8b1ff39180079)

ADL was here!
ADL was here!

History:

  1. I noticed operator& was the problem, but std::addressof() was ok!
  2. I noticed use of any (overloaded) operators seems to trigger this behaviour

This lead me to my 'Hypothesis II' (see above)

sehe
  • 374,641
  • 47
  • 450
  • 633
  • Proving it was the ADL, using [the ADL-blocking trick on _unqualified-id_](http://liveworkspace.org/code/aa79a478b00bfe9d89db62199c27abf9) – sehe Oct 16 '12 at 20:20
  • Added relevant standards citation. I think this should do. (I'll work on the formatting a bit) – sehe Oct 16 '12 at 20:26
  • 1
    Ok, I see. Thank you for the reference to the standard clause and explanation. – Nikki Chumakov Oct 16 '12 at 20:45
  • @NikkiChumakov I added more accurate note that this A is **not** fully instantiated, and how this can be demonstrated. I reduced the sample, showing also how`A` is indeed being used as a lookup namespace for `freefunction` in `freefunction(a)`. [Proof is in the live output here](http://liveworkspace.org/code/4f85a06598eebe1d8060112be36f4a29): _`ADL was here!`_ – sehe Oct 16 '12 at 21:08
  • @sehe Unfortunately I cannot use the suggested workaround. I have to use CRTP instead. It works but I do not really understand how and why (-: See the update2 in my question. – Nikki Chumakov Oct 18 '12 at 23:10
  • Re **update 1**: the compiler needs to _shallow instantiate_ the namespace of `A` in order to scan for applicable `operator&` overloads. Imagine if in my answer, `freefunction` would have been called `operator&` instead. (Shallow instantiation is enough to trigger compilation error on the typedef.) End of story :) – sehe Oct 19 '12 at 00:12
  • I'm sorry, but: WTF is that "ADL" thing? – akappa Oct 19 '12 at 07:43
  • @akappa Firstly, please watch your language. Secondly, ADL = argument-dependent-lookup. You know, the thing that makes [`std::cout << "Hello world" << endl;`](http://stackoverflow.com/a/7965277/85371) work... See also: [List of C++ name resolution (and overloading) rules](http://stackoverflow.com/questions/7374588/list-of-c-name-resolution-and-overloading-rules) – sehe Oct 19 '12 at 08:18
  • @sehe: sorry for the bad language and thanks for the explanation. – akappa Oct 19 '12 at 10:40
  • @sehe ideone uses gcc 4.3 which is too old for my example. Please try http://liveworkspace.org/code/dd26c903dc201b9b8a40da1665802edf instead. boost::bind also gives the similar error in this context with recent gcc: http://liveworkspace.org/code/6c5d8c8d79391c65bb6fbd9e5590f13a – Nikki Chumakov Oct 19 '12 at 14:25
  • @NikkiChumakov Duh. I expressly used an older compiler to match _your use of tr1 libraries_. If you had tried, you would have seen that exactly the same code [works fine on LWS](http://liveworkspace.org/code/e2e3e55c3e722f831bd17c96df191221), as is the [version using `boost::bind`](http://liveworkspace.org/code/d61b43fa5c46df12870d3d7baa1111f0)... So, my sample works across gcc 4.5.3 (c++03 and c++0x mode) as well as 4.7.2 (c++11 mode). The problem must be elsewhere. Perhaps even _due to_ the `derived()` hack? (I haven't analyzed that, to be honest) – sehe Oct 19 '12 at 14:42
  • @sehe You created bind object instance, but you did not actually call it. Compare **bind(...)** vs **bind(...) ()**. – Nikki Chumakov Oct 19 '12 at 16:53
  • Woot. Good call. I might give it another look to see what can be improved. Sorry for my misunderstanding (allthough it _was_ something else, in a way :)) – sehe Oct 19 '12 at 17:11