3

我正在尝试创建一个函数:

template <typename T>
void doIt( T*& p )
{
   if ( !p ) { return; }
   T& ref = *p;
   getClassName( ref );
}

其中行为根据p传入的类型而有所不同。特别是,被getClassName调用的版本应该取决于p. 在以下示例中,我可以成功调用:

doIt<myClass1>( myClass1*& )
doIt<myClass1<int> >( myClass1*& )
doIt<myClass2>( myClass2*& )
doIt<myClass2<int> >( myClass2*& )

但是当我打电话时它失败了:

doIt< std::vector<int, std::allocator<int> > >( std::vector<int, std::allocator<int>>*& )

出现错误:

a.cxx: In function ‘void doIt(T*&) [with T = std::vector<int, std::allocator<int> >]’:
ba.cxx:87:   instantiated from here
a.cxx:33: error: invalid initialization of reference of type ‘MyClass1&’ from expression of type ‘std::vector<int, std::allocator<int> >’
a.cxx:16: error: in passing argument 1 of ‘const char* getClassName(MyClass1&)’

(GCC 4.2.4)。

如果我移动以下声明:

template<typename T, typename A>
char const* getClassName( std::vector<T,A>& ) { printf("std::vector<T,A>\n"); return NULL; }

在 doIt 之前 - 然后它编译。所以,

  • 为什么需要getClassName( std::vector<T,A>& )出现在之前doIt而不是getClassName( MyClass2T<T>& )
  • 我该怎么做才能doIt独立std::vector?(我希望能够放置doIt在自己的标题中,而不必了解std::vector或任何专业化,这将是用户定义的)。

.

#include <stdio.h>
#include <assert.h>
#include <vector>

//template<typename T>
//char const* getClassName( T& );

//template<typename T, typename A>
////char const* getClassName( std::vector<T,A>& ) { printf("std::vector<T,A>\n"); return NULL; }

#if 1
// ---------  MyClass2
struct MyClass1
{};

char const* getClassName( MyClass1& ) { printf("MyClass1\n"); return NULL; }

// ---------  MyClass1T
template< typename T>
struct MyClass1T
{};

template<typename T>
char const* getClassName( MyClass1T<T>& ) { printf("MyClass1T<T>\n"); return NULL; }
#endif


template <typename T>
void doIt( T*& p )
{
   if ( !p ) { return; }
   T& ref = *p;
   getClassName( ref );
}


// ---------  MyClass2
struct MyClass2
{};


// declared after doIt, OK.
char const* getClassName( MyClass2& ) { printf("MyClass2\n"); return NULL; }

// ---------  MyClass2T
template< typename T>
struct MyClass2T
{};

// declared after doIt, OK.
template<typename T>
char const* getClassName( MyClass2T<T>& ) { printf("MyClass2T<T>\n"); return NULL; }

template<typename T, typename A>
char const* getClassName( std::vector<T,A>& ) { printf("std::vector<T,A>\n"); return NULL; }



void test()
{
#if 1
   MyClass1 mc1;
   MyClass1* mc1p = &mc1;
   doIt( mc1p );

   MyClass2 mc2;
   MyClass2* mc2p = &mc2;
   doIt( mc2p );

   MyClass1T<int> mc1t;
   MyClass1T<int>* mc1tp = &mc1t;
   doIt( mc1tp );

   MyClass2T<int> mc2t;
   MyClass2T<int>* mc2tp = &mc2t;
   doIt( mc2tp );

   // Nested templates are OK.
   MyClass2T<MyClass1> mc2t2;
   MyClass2T<MyClass1>* mc2tp2 = &mc2t2;
   doIt( mc2tp2 );
#endif

#if 1
   std::vector<int> v;
   std::vector<int>* vp = &v;
   doIt( vp );                   // FAIL!
#endif
}
4

2 回答 2

4

为什么要求 getClassName( std::vector& ) 出现在 doIt 之前而不是 getClassName( MyClass2T& )

任何函数都需要范围内的声明。当您使用 a 实例化模板函数时,vector<int>它期望存在具有签名的函数getClassName(vector<int>&)(至少是原型)以使编译成功。

我该怎么做才能使 doIt 独立于 std::vector?(我希望能够将 doIt 放在它自己的标题中,而不必了解 std::vector 或任何用户定义的专业化)

阅读有关模板的常见问题解答。尝试将所有doIt依赖模板函数的原型放在doIt.

于 2009-04-29T21:05:16.017 回答
4

失败的原因是在实例化时,没有发生对函数的非限定名称查找(但只有 ADL - Argument Dependent Lookup)。实例化上下文是(取自14.6.4.1/6C++ 标准):

依赖于模板参数的表达式的实例化上下文是在同一翻译单元中模板特化的实例化点之前声明的具有外部链接的声明集。

在这种情况下,您调用的所有模板特化的实例化点就在test(read 14.6.4.1/1) 的定义之后。因此,您声明的所有函数在test使用非限定查找的函数中都是可见的,但是对于函数调用,它们的查找实际上是不同的:

在模板中查找依赖于模板参数的函数调用如下:

  • 来自模板定义上下文的名称被普通查找和 ADL 考虑。
  • 实例化上下文中的名称仅适用于 ADL。

这意味着因为getClassName在模板的定义上下文中没有声明合适的函数,所以必须使用 ADL 在实例化上下文中找到合适的函数 - 否则调用将失败并且找不到任何声明。

参数相关查找 (ADL)

对于类型的参数std::vector<T>,ADL 搜索命名std空间中的函数和T. 将getClassName函数放入std命名空间将为此工作(但标准不允许这样做,因为这会导致未定义的行为 - 这应该仅作为最后的手段)。

要查看ADL尝试doIt使用向量MyClass2而不是调用的效果int。从那时起T = MyClass2,ADL 将在命名空间中搜索MyClass2接受 a 的合适函数std::vector<MyClass2>并成功 - 与您使用时相反int,它只会查找std

对于其他函数调用,也都找到了它们各自的声明,因为它们都声明在全局命名空间中,其中也定义了函数调用的参数类型(MyClass1MyClass2)。

C++ FAQ 很好,但它没有深入到模板中(没有发现任何关于 ADL 的提及)。有一个专门的模板常见问题解答可以处理一些更复杂的陷阱。


小心未定义的行为

请注意,即使您将我显示的声明放在函数之后(而不是之前),许多编译器也会接受代码test。但是正如上面的标准引用所说,那么声明将不会成为实例化上下文的一部分,并且14.6.4.2/1要观察其中的规则:

如果调用格式错误或找到更好的匹配,则在关联命名空间中的查找考虑所有翻译单元中这些命名空间中引入的具有外部链接的所有函数声明,而不仅仅是考虑在模板定义和模板中找到的那些声明实例化上下文,则程序具有未定义的行为。

因此,似乎起作用的是未定义的行为。编译器接受它是有效的,但拒绝它或崩溃和终止也同样有效。因此请注意,在实例化上下文中确实可以看到所需的任何名称,如解释的那样。

希望这可以帮助。

于 2009-04-29T23:05:08.287 回答