函数模板声明顺序影响可见性(有时)
我正在尝试创建一个函数:
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
出现在& ) doIt
之前,而不是getClassName( MyClass2T
& ; ) - 如何使
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
}
I'm trying to create a function:
template <typename T>
void doIt( T*& p )
{
if ( !p ) { return; }
T& ref = *p;
getClassName( ref );
}
where the behavior varies according to the type of p
passed in. In particular, the version of getClassName
called should depend upon the type of p
. In the following example, I can successfully call:
doIt<myClass1>( myClass1*& )
doIt<myClass1<int> >( myClass1*& )
doIt<myClass2>( myClass2*& )
doIt<myClass2<int> >( myClass2*& )
but it fails when I call:
doIt< std::vector<int, std::allocator<int> > >( std::vector<int, std::allocator<int>>*& )
with the error:
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).
If I move the declaration of:
template<typename T, typename A>
char const* getClassName( std::vector<T,A>& ) { printf("std::vector<T,A>\n"); return NULL; }
before doIt -- then it compiles. So,
- Why is it required that
getClassName( std::vector<T,A>& )
appears beforedoIt
but notgetClassName( MyClass2T<T>& )
- What can I do to make
doIt
independent ofstd::vector
? (I want to be able to placedoIt
in its own header and not have to know aboutstd::vector
, or any of the specializations, which will be user-defined).
.
#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
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
任何函数都需要在作用域中进行声明。 当您使用
vector
实例化模板函数时,它期望存在具有签名getClassName(vector&)
的函数(至少有一个原型) ) 以使编译成功。) parashift.com/c++-faq-lite/templates.html" rel="nofollow noreferrer">模板常见问题解答。 尝试在第一次实例化
doIt
之前放置所有doIt
的依赖模板函数的原型。A declaration in scope is required for any function. When you instantiate your template function with a
vector<int>
it expects a function with the signaturegetClassName(vector<int>&)
to be present (at least a prototype) for compilation to succeed.Read the FAQ on Templates. Try putting the prototype of all of
doIt
's dependent template functions before first instantiation ofdoIt
.失败的原因是在实例化时,不会发生函数的非限定名称查找(但仅发生 ADL - 参数相关查找)。 实例化上下文是(取自 C++ 标准的
14.6.4.1/6
):在这种情况下,您调用的所有模板专业化的实例化点就在
test
的定义之后(请阅读14.6.4.1/1
)。 因此,您声明的所有函数在使用非限定查找的test
函数中都是可见的,但对它们的查找实际上与函数调用不同:查找依赖于模板内模板参数的函数调用像这样:
这意味着,由于在模板的定义上下文中没有声明合适的
getClassName
函数,因此必须使用 ADL 在实例化上下文中找到合适的函数 - 否则调用将失败并且找不到任何声明。参数相关查找 (ADL)
对于
std::vector
类型的参数,ADL 在命名空间std
和T
命名空间中搜索函数代码>. 将getClassName
函数放入std
命名空间中可以实现此目的(但标准不允许这样做,因为这会产生未定义的行为 - 这只应在以下情况下完成)最后一招)。要查看
ADL
的效果,请尝试使用MyClass2
向量(而不是int
)调用doIt
。 从那时起T = MyClass2
,ADL 将在MyClass2
的命名空间中搜索接受std::vector
的合适函数,并将success - 与使用int
相反,后者只会查看std
。对于其他函数调用,也都找到了它们各自的声明,因为它们都是在全局命名空间中声明的,其中也定义了函数调用的参数类型(
MyClass1
,MyClass2
等)。C++ FAQ 很好,但它没有深入讨论模板(没有发现其中提到 ADL)。 有一个专门的模板常见问题解答,可以处理一些更复杂的陷阱。
注意未定义的行为
请注意,即使您将我显示的声明放在
test
函数之后(而不是之前),许多编译器也会接受代码。 但正如上面的标准引用所说,那么声明将不会成为实例化上下文的一部分,并且需要监视14.6.4.2/1
中找到的规则:因此,看似有效的行为将是未定义的行为。 编译器接受它是有效的,但拒绝它或崩溃并终止同样有效。 因此,请注意,所需的任何名称确实在实例化上下文中可见,如所解释的那样。
希望这可以帮助。
The reason for the failure is that at instantiation, no unqualified name lookup for the functions occur (but only ADL - Argument Dependent Lookup). The instantiation context is (taken from
14.6.4.1/6
of the C++ Standard):The point of instantiation of all those template specializations you called in this case is just after the definition of
test
(read14.6.4.1/1
). So, all functions you declared are visible in yourtest
function using unqualified lookup, but lookup for them is actually different for the function calls:A function call that depends on a template parameter within a template is looked up like this:
This means that because there is no suitable
getClassName
function declared in the definition context of the template, a suitable function has to be found in the instantiation context using ADL - otherwise the call will fail and not find any declaration.Argument Dependent Lookup (ADL)
For an argument of type
std::vector<T>
, ADL searches for functions in namespacestd
and the namespace ofT
. Putting thegetClassName
function into thestd
namespace would work for this (but doing so is not allowed by the Standard because that yields to undefined behavior - this should be done only as the last resort).To see the effects of
ADL
try to calldoIt
with a vector ofMyClass2
instead ofint
. Since thenT = MyClass2
, ADL will search in the namespace ofMyClass2
for a suitable function accepting astd::vector<MyClass2>
and will succeed - opposed to when you useint
, which will only look intostd
.For the other function calls, their respective declarations are all found too, because they are all declared in the global namespace, in which the argument types of the function calls are defined too (
MyClass1
,MyClass2
etc).The C++ FAQ is good, but it doesn't go deep into templates (haven't found any mentioning of ADL in it). There is a dedicated template faq that handles some of the more intricate pitfalls.
Beware of undefined behavior
Note that many compilers will accept the code even when you put that declaration i showed after the
test
function (instead of before it). But as the above Standard quote says, then the declaration won't be part of the instantiation context and the rule found in14.6.4.2/1
is to be watched:Thus, what would appear to work would be undefined behavior. It's valid for a compiler to accept it, but it's likewise valid for one to reject it or to crash and terminate. So watch that any name needed is indeed visible at the instantiation context as explained.
Hope this helps.