函数模板声明顺序影响可见性(有时)

发布于 2024-07-19 00:48:23 字数 3594 浏览 5 评论 0原文

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

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 before doIt but not getClassName( MyClass2T<T>& )
  • What can I do to make doIt independent of std::vector? (I want to be able to place doIt in its own header and not have to know about std::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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

一场信仰旅途 2024-07-26 00:48:23

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

任何函数都需要在作用域中进行声明。 当您使用 vector 实例化模板函数时,它期望存在具有签名 getClassName(vector&) 的函数(至少有一个原型) ) 以使编译成功。

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

) parashift.com/c++-faq-lite/templates.html" rel="nofollow noreferrer">模板常见问题解答。 尝试在第一次实例化 doIt 之前放置所有 doIt 的依赖模板函数的原型。

Why is it required that getClassName( std::vector& ) appear before doIt but not getClassName( MyClass2T& )

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 signature getClassName(vector<int>&) to be present (at least a prototype) for compilation to succeed.

What can I do do make doIt independent of std::vector? (I want to be able to place doIt in its own header and not have to know about std::vector, or any of the specializations, which will be user-defined)

Read the FAQ on Templates. Try putting the prototype of all of doIt's dependent template functions before first instantiation of doIt.

萤火眠眠 2024-07-26 00:48:23

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

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

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

查找依赖于模板内模板参数的函数调用像这样:

  • 普通查找和 ADL 都会考虑模板定义上下文中的名称。
  • 仅 ADL 考虑实例化上下文中的名称。

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

参数相关查找 (ADL)

对于 std::vector 类型的参数,ADL 在命名空间 stdT 命名空间中搜索函数代码>. 将 getClassName 函数放入 std 命名空间中可以实现此目的(但标准不允许这样做,因为这会产生未定义的行为 - 这只应在以下情况下完成)最后一招)。

要查看 ADL 的效果,请尝试使用 MyClass2 向量(而不是 int)调用 doIt。 从那时起 T = MyClass2,ADL 将在 MyClass2 的命名空间中搜索接受 std::vector 的合适函数,并将success - 与使用 int 相反,后者只会查看 std

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

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 instantiation context of an expression that depends on the template arguments is the set of declarations with external linkage declared prior to the point of instantiation of the template specialization in the same translation unit.

The point of instantiation of all those template specializations you called in this case is just after the definition of test (read 14.6.4.1/1). So, all functions you declared are visible in your test 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:

  • Names from the template definition context are considered by both ordinary lookup and ADL.
  • Names from instantiation context are considered only for ADL.

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 namespace std and the namespace of T. Putting the getClassName function into the std 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 call doIt with a vector of MyClass2 instead of int. Since then T = MyClass2, ADL will search in the namespace of MyClass2 for a suitable function accepting a std::vector<MyClass2> and will succeed - opposed to when you use int, which will only look into std.

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 in 14.6.4.2/1 is to be watched:

If the call would be ill-formed or would find a better match had the lookup within the associated namespaces considered all the function declarations with external linkage introduced in those namespaces in all translation units, not just considering those declarations found in the template definition and template instantiation contexts, then the program has undefined behavior.

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文