【问题标题】:C++ linking and template specializationsC++ 链接和模板特化
【发布时间】:2011-08-25 12:34:28
【问题描述】:

我正在研究 C++ 链接器在模板特化方面的行为。我正在使用 Microsoft Visual C++ 2010 进行这些实验。我不知道其他工具链(例如 gcc)的行为是否相同。

这是第一个代码sn-p:

// bar.cpp

template <typename T> int foo() { return 1; }
int bar() { return foo<double>(); }

// main.cpp

template <typename T> int foo() { return 1; }
template <> int foo<double>() { return 2; }

int bar();

int main()
{
    const int x = bar();
    const int y = foo<double>();  // doesn't link
}

预计,这段代码没有链接,因为foo&lt;double&gt;() 有多个定义,因为它在 bar.cppmain.cpp 中被实例化一次(通过专门化)。然后我们会期望,如果这个程序链接bar()main() 将使用 foo() 的不同实例化,这样最后我们会得到 x == 1 和 y == 2.

让我们通过将foo&lt;double&gt;() 的特化声明为static 来修复链接错误:

// bar.cpp

template <typename T> int foo() { return 1; }
int bar() { return foo<double>(); }

// main.cpp

template <typename T> int foo() { return 1; }
template <> static int foo<double>() { return 2; }  // note: static

int bar();

int main()
{
    const int x = bar();          // x == 1
    const int y = foo<double>();  // y == 2
}

正如我们预期的那样,我们现在有 x == 1 和 y == 2。 (注意:我们必须在此处使用static 关键字:匿名命名空间不会这样做,因为我们不能将模板函数专门化到与其声明不同的命名空间中。)

现在,static 关键字的使用相当不直观。通常,特化 foo&lt;double&gt;() 将驻留在头文件中的某个位置,因此将被标记为内联,如以下 sn-p:

// bar.cpp

template <typename T> int foo() { return 1; }
int bar() { return foo<double>(); }

// main.cpp

template <typename T> int foo() { return 1; }
template <> inline int foo<double>() { return 2; }  // note: inline

int bar();

int main()
{
    const int x = bar();          // x == 2
    const int y = foo<double>();  // y == 2
}

这段代码现在可以正确链接,当我们运行它时,我们会得到 x == 2 和 y == 2。这让我感到惊讶:为什么foo&lt;double&gt;() 有一个定义?这段代码中inline是什么意思?

最后一个sn-p:

// bar.cpp

template <typename T> int foo() { return 1; }
int bar() { return foo<double>(); }

// main.cpp

template <typename T> int foo() { return 1; }
template <> inline int foo<double>() { return 2; }  // note: inline

int bar();

int main()
{
    const int x = bar();             // x == 1
    // const int y = foo<double>();  // note: commented out
}

这种情况其实并不奇怪:foo&lt;double&gt;() 的特化不再在 main.cpp 中实例化(虽然声明还在),所以唯一剩下的实例化就是 main.cpp 中的那个em>bar.cpp.

【问题讨论】:

  • 在您的第三段中,bar.cpp 中的调用不是“隐式专业化”,而是“实例化”,因为您只 调用 foo&lt;double&gt;()(您是不专攻任何东西)。此外,下一句,“显式专业化”可能应该只是“专业化”或“完全专业化”,以避免与听起来相似的“显式实例化”(您没有)混淆。
  • 我想如果你inline一个函数,你保证它在所有翻译单元中都有相同的定义。如果这是真的,那么您将招致未定义的行为。
  • @Kerrek 实际上这不是真的。允许内联函数在不同的翻译单元中有不同的实现。它们是单一定义规则的一个例外。
  • @Let_Me_Be:不,他们不是。此类事物的每个定义都必须包含相同的标记序列,否则您将有未定义的行为。当违反单一定义规则的某些方面时,工具链不需要给出任何诊断。
  • @Let_Me_Be:内联函数不能免除单一定义规则。函数模板是,但不是(完整的)函数模板特化。

标签: c++ templates linker inline specialization


【解决方案1】:

您实际上在这里违反了 C++ 规则(强调我的):

14.7.3 [temp.expl.spec]

6/ 如果模板、成员模板或类模板的成员被显式特化,则应在第一次使用该特化之前声明该特化,这将导致隐式实例化放置在发生这种使用的每个翻译单元中; 无需诊断。如果程序 没有为显式特化提供定义,并且特化的使用方式会导致隐式实例化或成员是虚拟成员函数,程序格式错误,不需要诊断。永远不会为已声明但未定义的显式特化生成隐式实例化。 [例子

class String { };

template<class T> class Array { /* ... */ };
template<class T> void sort(Array<T>& v) { /* ... */ }

void f(Array<String>& v) {
  sort(v); // use primary template
  // sort(Array<T>&), T is String
}

template<> void sort<String>(Array<String>& v); // error: specialization
                                                // after use of primary template

template<> void sort<>(Array<char*>& v); // OK: sort<char*> not yet used

template<class T> struct A {
  enum E : T;
  enum class S : T;
};

template<> enum A<int>::E : int { eint }; // OK
template<> enum class A<int>::S : int { sint }; // OK
template<class T> enum A<T>::E : T { eT };
template<class T> enum class A<T>::S : T { sT };
template<> enum A<char>::E : int { echar }; // ill-formed,
                                            // A<char>::E was instantiated
                                            // when A<char> was instantiated
template<> enum class A<char>::S : int { schar }; // OK

结束示例 ]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-11-06
    • 2022-07-05
    • 2012-12-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-08
    • 1970-01-01
    相关资源
    最近更新 更多