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

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&)’

(海湾合作委员会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
}
有帮助吗?

解决方案

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

任何函数都需要范围声明。当你用一个实例化你的模板函数时 vector<int> 它需要一个带有签名的函数 getClassName(vector<int>&) 存在(至少是原型)以便编译成功。

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

阅读 模板常见问题解答. 。尝试把所有的原型 doIt的依赖模板函数在第一次实例化之前 doIt.

其他提示

失败的原因是在实例化时,不会发生函数的非限定名称查找(但仅发生 ADL - 参数相关查找)。实例化上下文是(取自 14.6.4.1/6 C++ 标准):

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

在这种情况下,您调用的所有模板专业化的实例化点就在定义之后 test (读 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 对于接受一个合适的函数 std::vector<MyClass2> 并且将会成功 - 与您使用时相反 int, ,这只会调查 std.

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

C++ FAQ 很好,但它没有深入讨论模板(没有发现其中提到 ADL)。有一个专门的 模板常见问题解答 这可以解决一些更复杂的陷阱。


谨防未定义的行为

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

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

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

希望这可以帮助。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top