未定义的模板方法技巧?

发布于 2024-09-01 00:59:46 字数 1602 浏览 8 评论 0原文

我的一位同事告诉我,他在团队中使用的一个小设计让我心潮澎湃。这是一种traits类,他们可以以一种极其解耦的方式专门化。

我很难理解它是如何工作的,而且我仍然不确定我的想法,所以我想我会在这里寻求帮助。

我们在这里谈论 g++,特别是版本 3.4.2 和 4.3.2(似乎两者都适用)。

这个想法很简单:

1- 定义接口

// interface.h
template <class T>
struct Interface
{
  void foo(); // the method is not implemented, it could not work if it was
};

//
// I do not think it is necessary
// but they prefer free-standing methods with templates
// because of the automatic argument deduction
//
template <class T>
void foo(Interface<T>& interface) { interface.foo(); }

2- 定义一个类,并在源文件中专门化该类的接口(定义其方法)

// special.h

class Special {};


// special.cpp

#include "interface.h"
#include "special.h"

// 
// Note that this specialization is not visible outside of this translation unit
//
template <>
struct Interface<Special>
{
  void foo() { std::cout << "Special" << std::endl; }
};

3- 使用起来也很简单:

// main.cpp

#include "interface.h"

class Special; // yes, it only costs a forward declaration
               // which helps much in term of dependencies

int main(int argc, char* argv[])
{
  Interface<Special> special;
  foo(special);
  return 0;
};

如果没有翻译单元,它是一个未定义的符号为Special 定义了Interface 的特化。

现在,我本以为这需要 export 关键字,据我所知,该关键字从未在 g++ 中实现过(并且只在 C++ 编译器中实现过一次,其作者建议任何人不要这样做,因为他们花费了时间和精力)。

我怀疑这与链接器解析模板方法有关...

  • 您以前遇到过类似的事情吗?
  • 它符合标准还是你认为它有效是一个幸运的巧合?

我必须承认我对这个结构感到非常困惑......

A colleague of mine told me about a little piece of design he has used with his team that sent my mind boiling. It's a kind of traits class that they can specialize in an extremely decoupled way.

I've had a hard time understanding how it could possibly work, and I am still unsure of the idea I have, so I thought I would ask for help here.

We are talking g++ here, specifically the versions 3.4.2 and 4.3.2 (it seems to work with both).

The idea is quite simple:

1- Define the interface

// interface.h
template <class T>
struct Interface
{
  void foo(); // the method is not implemented, it could not work if it was
};

//
// I do not think it is necessary
// but they prefer free-standing methods with templates
// because of the automatic argument deduction
//
template <class T>
void foo(Interface<T>& interface) { interface.foo(); }

2- Define a class, and in the source file specialize the interface for this class (defining its methods)

// special.h

class Special {};


// special.cpp

#include "interface.h"
#include "special.h"

// 
// Note that this specialization is not visible outside of this translation unit
//
template <>
struct Interface<Special>
{
  void foo() { std::cout << "Special" << std::endl; }
};

3- To use, it's simple too:

// main.cpp

#include "interface.h"

class Special; // yes, it only costs a forward declaration
               // which helps much in term of dependencies

int main(int argc, char* argv[])
{
  Interface<Special> special;
  foo(special);
  return 0;
};

It's an undefined symbol if no translation unit defined a specialization of Interface for Special.

Now, I would have thought this would require the export keyword, which to my knowledge has never been implemented in g++ (and only implemented once in a C++ compiler, with its authors advising anyone not to, given the time and effort it took them).

I suspect it's got something to do with the linker resolving the templates methods...

  • Do you have ever met anything like this before ?
  • Does it conform to the standard or do you think it's a fortunate coincidence it works ?

I must admit I am quite puzzled by the construct...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

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

发布评论

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

评论(2

夏天碎花小短裙 2024-09-08 00:59:46

就像@Steward 怀疑的那样,它是无效的。从形式上来说,它有效导致未定义的行为,因为标准规则规定对于违规行为不需要诊断,这意味着实现可以默默地做任何它想做的事情。于14.7.3/6

如果模板、成员模板或类模板的成员被显式特化,则应在第一次使用该特化之前声明该特化,这将导致在每个翻译单元中发生隐式实例化发生使用;无需诊断。

实际上,至少在 GCC 上,它会隐式实例化主模板 Interface,因为专门化未声明且在 main 中不可见,然后调用 接口::foo。如果它的定义是可见的,它就会实例化成员函数的主要定义(这就是为什么当它被定义时,它不起作用)。

实例化的函数名称符号具有弱链接,因为它们可能在不同的目标文件中多次出现,并且必须在最终程序中合并为一个符号。相反,不再是模板的显式专业化的成员具有强链接,因此它们将支配弱链接符号并使调用最终在专业化中结束。所有这些都是实施细节,标准没有弱/强链接的概念。您必须在创建 special 对象之前声明专业化:

template <>
struct Interface<Special>;

标准将其暴露出来(我强调)

函数模板、类模板、类模板的成员函数、类模板的静态数据成员、类模板的成员类、类模板的成员类模板、类模板的成员函数模板、成员的显式专业化声明的放置类模板的成员模板的函数、非模板类的成员模板的成员函数、类模板的成员类的成员函数模板等,以及类模板、非模板的成员类模板的部分特化声明的放置类、类模板的成员类模板等可以根据显式专业化声明及其在翻译单元中的实例化点的相对定位(如上文和下文所指定)影响程序是否格式良好。 编写专业化时,请注意其位置;或者让它编译将是一个足以点燃其自焚的考验。

Like @Steward suspected, it's not valid. Formally it's effectively causing undefined behavior, because the Standard rules that for a violation no diagnostic is required, which means the implementation can silently do anything it wants. At 14.7.3/6

If a template, a member template or the member of a class template is explicitly specialized then that specialization shall be declared before the first use of that specialization that would cause an implicit instantiation to take place, in every translation unit in which such a use occurs; no diagnostic is required.

In practice at least on GCC, it's implicitly instantiating the primary template Interface<T> since the specialization wasn't declared and is not visible in main, and then calling Interface<T>::foo. If its definition is visible, it instatiates the primary definition of the member function (which is why when it is defined, it wouldn't work).

Instantiated function name symbols have weak linkage because they could possibly be present multiple times in different object files, and have to be merged into one symbol in the final program. Contrary, members of explicit specializations that aren't templates anymore have strong linkage so they will dominate weak linkage symbols and make the call end up in the specialization. All this is implementation detail, and the Standard has no such notion of weak/strong linkage. You have to declare the specialization prior to creating the special object:

template <>
struct Interface<Special>;

The Standard lays it bare (emphasize by me)

The placement of explicit specialization declarations for function templates, class templates, member functions of class templates, static data members of class templates, member classes of class templates, member class templates of class templates, member function templates of class templates, member functions of member templates of class templates, member functions of member templates of non-template classes, member function templates of member classes of class templates, etc., and the placement of partial specialization declarations of class templates, member class templates of non-template classes, member class templates of class templates, etc., can affect whether a program is well-formed according to the relative positioning of the explicit specialization declarations and their points of instantiation in the translation unit as specified above and below. When writing a specialization, be careful about its location; or to make it compile will be such a trial as to kindle its self-immolation.

梦途 2024-09-08 00:59:46

那非常整洁。我不确定它是否保证在任何地方都能工作。看起来他们正在做的是故意未定义模板方法,然后定义隐藏在自己的翻译单元中的专业化。它们依赖于编译器对原始类模板方法和专业化使用相同的名称重整,我认为这可能是非标准的。然后,链接器将查找类模板的方法,但会查找专门化。

但这也存在一些风险。例如,没有人,甚至链接器,会选择该方法的多个实现。模板方法将被标记为 selectany,因为模板意味着内联,因此如果链接器看到多个实例,它不会发出错误,而是选择最方便的一个。

不过,这仍然是一个不错的技巧,尽管不幸的是,它的奏效似乎确实是一个幸运的巧合。

Thats pretty neat. I'm not sure if it is guaranteed to work everywhere though. It looks like what they're doing is having a deliberately undefined template method, and then defining a specialization tucked away in its own translation unit. They're depending on the compiler using the same name mangling for both the original class template method and the specialization, which is the bit I think is probably non-standard. The linker will then look for the method of the class template, but instead find the specialization.

There are a few risks with this though. No one, not even the linker, will pick up multiple implementations of the method for example. The template methods will be marked selectany because template implies inline so if the linker sees multiple instances, instead of issuing an error it will pick whichever one is most convenient.

Still a nice trick though, although unfortunately it does seem to be a lucky coincidence that it works.

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