获取模板类对象的地址会导致模板参数的完全实例化

时间:2012-10-16 18:07:37

标签: c++ templates gcc instantiation

我得到了使用g ++ 4.6和4.8编译此代码的错误。 g ++ 4.2和4.4就可以了。这是一个bug还是一些新的语言功能?

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;
                                  ^

更新1 :我知道A无法实例化。

问题是:为什么编译器试图在“(&amp; * this) - &gt; bar()”行中实例化它,而不是“this-&gt; bar()”或“(* this)。 bar()“lines?

更新2

addressof (object)的建议解决方法对我不起作用,因为实际上当我尝试使用std::bind (&B::bar, this)时出现错误。真正的代码当然要复杂得多,并且bind没有单独使用,但问题可以追溯到简单的std::bind表达式。

我不想重写或重新发明std::bind,因此我必须使用 CRTP 才能使其正常工作:

#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;
}

我发现这些错误和解决方法完全不合逻辑。

1 个答案:

答案 0 :(得分:11)

分析/解释:

您所看到的是实例化,而不是已满(请参阅下面的证明)。

ADL是罪魁祸首。

  

假设II 我怀疑这里有一个与ADL相关的东西(类可以有内联声明的静态自由函数(朋友)。也许编译器需要实例化整个类模板才能确保它已经看到了在其中声明的运算符重载(为了进行重载解析)。

标准支持我:§3.4.2(n3337中的第46页):

  

² [snip] 命名空间和类的集合完全取决于   函数参数的类型(以及任何模板的命名空间)   模板参数)。 [snip] 名称空间和类的集合是   通过以下方式确定:

     
      
  • [剪断]

  •   
  • 如果T是类类型(包括联合),则其关联的类是:   阶级本身;它所属的成员,如果有的话;及其直接和   间接基类。其关联的命名空间是其名称空间   其关联的类是成员。此外,如果T是类模板   特化,其关联的命名空间和类还包括:   与模板类型相关联的名称空间和类   为模板类型参数提供的参数(不包括模板   模板参数);任何模板模板参数的名称空间   是会员;以及任何成员模板用作模板的类   模板参数是成员。

  •   

粗体短语包含class A<void>作为ADL的查找命名空间。

解决方法:

在您的情况下,可以使用std::addressof(b)代替&b,它会起作用。

演示:

请参阅http://liveworkspace.org/code/4f85a06598eebe1d8060112be36f4a29

注意: (unqualified-id) 技巧在标准的§3.4.2中定义。

#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
}

打印

ADL was here!

此外,您可以验证模板参数(A<void>)仅获得浅实例化将格式错误的typedef移动到成员函数中可以解决问题:

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

输出( http://liveworkspace.org/code/a15c933293281d0926e8b1ff39180079

ADL was here!
ADL was here!

历史:

  1. 我注意到operator&是问题所在,但std::addressof()还可以!
  2. 我注意到使用任何(重载)运算符似乎会触发此行为
  3. 这引导我进入我的'假设II'(见上文)