在源文件中放置模板成员函数的专业化定义(没有默认主体)是否安全? [英] Is it safe to place definition of specialization of template member function (withOUT default body) in source file?

查看:47
本文介绍了在源文件中放置模板成员函数的专业化定义(没有默认主体)是否安全?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是我的意思:

// test.h
class cls
{
public:
    template< typename T >
    void f( T t );
};

-

// test.cpp
template<>
void cls::f( const char* )
{
}

-

// main.cpp
int main()
{
    cls c;

    double x = .0;
    c.f( x ); // gives EXPECTED undefined reference (linker error)

    const char* asd = "ads";
    c.f( asd ); // works as expected, NO errors

    return 0;
}

这完全可以,对吧?

我开始对此表示怀疑,因为我只是在实例化错误后才碰到了'...'的专业化错误,这对我来说是新的.因此,我解决"了该错误,现在一切似乎都可以正常工作,但是仍然..

I started doubting this, because I just ran over the specialization of '...' after instantiation error, which was new to me. So, I "worked around" this error and everything seems to work fine now, but still..

这是行为明确的行为吗?

Is this well-defined behavior?

编辑:对于非成员模板函数(转发声明的非成员模板函数)也是如此.

edit: And the same for non-member template functions (forward declared non-member template functions).

推荐答案

轨道轻度竞赛引用了为什么它不符合标准的部分.附近可能还会有其他人.

Lightness Races in Orbit cited why it's not compliant parts from the Standard. There might be some others, in the vicinity.

我将尝试用更简单的术语来解释Standard verbiage的含义,并希望我能正确理解它,最后解释链接器错误(或没有错误):

I will try to explain in simpler terms what the Standard verbiage means, and hopefully I'll get it correctly, and finally explain the linker errors (or absence of error):

  1. 实例化的重点是什么?
  2. 编译器如何选择专业化?
  3. 实例化时需要什么?
  4. 为什么出现链接器错误?


1/实例化的重点是什么?

模板函数的实例化点是使用模板 all 调用或引用它的点(& std :: sort< Iterator> )参数充实(*).

The point of instantiation of a template function is the point where it is called or referred to (&std::sort<Iterator>) with all the template parameters fleshed out (*).

template <typename T>
void foo(T) { std::cout << typeid(T).name() << "\n"; }

int main() { foo(1); } // point of instantiation of "foo<int>(int)"

对于从其他模板调用的模板,它可能会延迟,因此与确切的调用站点不匹配:

It can be delayed though, and thus not match the exact call site, for templates called from other templates:

template <typename T>
void foo(T) { std::cout << typeid(T).name() << "\n"; }

template <typename T>
void bar(T t) { foo(t); } // not a point of instantiation, T is still "abstract"

int main() { foo(1); } // point of instantiation of "bar<int>(int)"
                       // and ALSO of "foo<int>(int)"

此延迟非常重要,因为它可以进行写入:

This delay is very important as it enables writing:

  • 共同递归模板(即互相引用的模板)
  • 用户专业化

(*)粗略地说,有一些例外,例如模板类的非模板方法...

(*) Roughly speaking, there are exceptions such as non-template methods of a template class...

2/编译器如何选择特殊化?

在实例化时,编译器需要能够:

At the point of instantiation, a compiler need to be able to:

  • 确定要调用的基本模板函数
  • 以及可能要呼叫的哪个专业

这个古老的 GotW 展示了专业化的困扰...但总之:

This old GotW shows off the woes of specializations... but in short:

template <typename T> void foo(T);   // 1
template <typename T> void foo(T*);  // 2

超载,并且每个都产生了一个不同的家族,它们是 base 的可能专长.

are overloads, and each spawns a distinct family of possible specializations of which they are the base.

template <> void foo<int>(int);

是1的特殊化,

template <> void foo<int*>(int*);

是2的专业化.

为了解决函数调用,编译器将首先选择最佳重载,而忽略模板特化,然后,如果选择了模板函数,则检查其是否具有可以更好地应用.

In order to resolve the function call, the compiler will first pick the best overload, while ignoring template specializations, and then, if it picked a template function, check if it has any specialization that could better apply.

3/在实例化时需要什么?

因此,从编译器解析调用的方式来看,我们理解为什么为什么标准指定在第一个实例化点之前声明任何特殊化.否则,它根本就不会被考虑.

So, from the way a compiler resolve the call, we understand why the Standard specifies that any specialization should be declared before its first point of instantiation. Otherwise, it simply would not be considered.

因此,在实例化时,需要已经看到:

Thus, at the point of instantiation, one needs to have already seen:

  • 要使用的基本模板函数的声明
  • 要选择的专业声明,如果有的话

但是定义如何?

不需要.编译器假定它将在以后的TU中提供,或者完全由另一个TU提供.

It is not needed. The compiler assumes it will either be provided later on in the TU or by another TU entirely.

注意:它确实使编译器负担重担,因为这意味着它需要记住它遇到的所有隐式实例,并且它们无法为其发出函数体,以便当它最终遇到该定义时,它可以(最后))发出它遇到的所有专业化所需的所有代码.我想知道为什么选择了这种特定方法,也想知道为什么即使没有 extern 声明,TU也会以未定义的函数体结尾.

Note: it does burden the compiler because it means it needs to remember all the implicit instantiations it encountered and for which it could not emit a function-body so that when it finally encounters the definition it can (at last) emit all the necessary code fo all the specializations it encountered. I wonder why this particular approach was selected, and also wonder why even in the absence of an extern declaration the TU may end with undefined function-bodies.

4/为什么出现链接器错误?

由于未提供任何定义,因此gcc信任您以后提供它,而只是发出对未解析符号的调用.如果您碰巧与另一个提供此符号的TU链接,那么一切都会好起来,否则会出现链接器错误.

Since no definition is provided, gcc trusts you to provide it later and simply emits a call to an unresolved symbol. If you happen to link with another TU that provides this symbol, then everything will be fine, and otherwise you'll get a linker error.

由于gcc遵循 Itanium ABI ,我们可以简单地查看它如何处理符号.事实证明,ABI在处理专业化和隐式实例化方面没有任何区别

Since gcc follows the Itanium ABI we can simply look up how it mangles the symbols. It turns out that the ABI makes no difference in mangling specializations and implicit instantiations thus

cls.f( asd );

调用 _ZN3cls1fIPKcEEvT _ (将其分解为 void cls :: f< char const *>(char const *))和专业化:

calls _ZN3cls1fIPKcEEvT_ (which demangles as void cls::f<char const*>(char const*)) and the specialization:

template<>
void cls::f( const char* )
{
}

还会生成 _ZN3cls1fIPKcEEvT _ .

注意:对我来说,尚不清楚是否可以对显式专业化进行不同的处理.

这篇关于在源文件中放置模板成员函数的专业化定义(没有默认主体)是否安全?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆