Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting the address of template class object leads to full instantiation of template parameters

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.

like image 622
Nikki Chumakov Avatar asked Oct 16 '12 18:10

Nikki Chumakov


1 Answers

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)

like image 156
sehe Avatar answered Nov 15 '22 16:11

sehe