【问题标题】:Can this be legally be done in C++?这可以在 C++ 中合法地完成吗?
【发布时间】:2011-05-03 20:32:55
【问题描述】:

注意:the following code is illegal, but a conforming compiler is not required to reject it(有些没有)。

在我正在使用的库中,我有一个Foo 的模板函数声明和一个foobar.hBar 的模板函数定义:

template<class C> int Foo();

template<class C> int Bar() {
  return Something(  Foo<C>()  );
}

意图是其他代码可以像这样使用它:

#include "foobar.h"

int main() {
  Bar<MyClass>();
  return 0;
}

// Ideally, the usage (above) and the definition (below)
// would/could be in different translation units. 

template<> int Foo<MyClass>() { return 5; }

问题: 有没有办法让这项工作也合法?


问题是(如果我理解正确的话)尽管编译,这在技术上是非法的:它违反了ODR,因为显式专业化和Bar&lt;MyClass&gt; 的使用都算作定义,尽管事实上在用例中没有可以使用的主体。

我想使用这种模式来参数化Foo 的原因是,作为我必须遵循的样式指南的结果,这是确保在Bar 的定义之前在词法上包含任何内容的唯一方法是为了将其包含在foobar.h 中。但是(出于我希望我不需要解释的原因)这是行不通的。

【问题讨论】:

  • Foo 的最后一个定义应该是什么意思?绝对不清楚您要做什么。提供更多详细信息。
  • 为什么不提供int(*)(void) 函数指针作为Bar 的模板参数,而不是C 类? template &lt;int(*FOO)()&gt; int Bar() { return Something(FOO()); }。然后用户可以指示直接调用什么函数,而不是通过将Foo 特化为MyClass 来间接调用。然后如果愿意,可以用仿函数类替换函数指针。
  • 我认为通常应该将免费函数模板声明为inline 以规避 ODR。
  • 不确定 ODR,但代码似乎违反了 14.7.3/6:“如果模板 ... 是显式特化的,则应在第一次使用该特化之前声明该特化导致发生隐式实例化..."
  • 一个想法:在 Bar 的定义之前没有要包含的特化,在第一次使用 Bar 之前就足够了,因为实例化点就在那里。 IE。我相信在main() 之前声明(甚至没有定义)专业​​化是合法的。也不是一个选择?

标签: c++ templates language-specifications one-definition-rule


【解决方案1】:

有没有办法让这项工作也合法?

是的,在使用之前声明特化。在您提供的示例中,只需交换文件中专业化和主要的顺序即可。但是,在示例中,您仍然必须确保没有其他 TU 使用该专业化而不声明它。

通常这些特化应该在标题中声明。

在您的示例的范围内(即没有根本性的改变),没有其他方法可以使这项工作。

【讨论】:

  • 这可以在我遇到这种情况的情况下工作,但不能很好地概括。 (参见我在第二个代码块中添加的注释)
  • @BCS:您评论中的“理想”情况是完全不可能的:必须先声明事物才能使用它们,并且在使用它的 TU 中不能缺少该函数的声明。
  • @BCS: 对比一下尝试的情况 void f() { this_function_not_declared(); } 没有在那个 TU 中声明被调用的函数。
  • 是的,它们必须在使用前声明,但我想要一个理想的解决方案是使用一个模板函数,该函数只有(并且,在那个 TU 中,只会)被声明 但未定义.
  • @BCS:没关系。使用前声明,定义一次。
【解决方案2】:

这是不合法的,因为不可能专门化模板函数。

你可以这样做:

template< typename T >
struct foo
{
  static int doSomething() {return 0;}
};
template< >
struct foo<int>
{
  static int doSomething() {return 5;}
};

【讨论】:

  • 当然,函数模板可以被特化,只是不能部分特化。
  • 我喜欢 do 的蓝色。 :-)
  • 有时我希望人们如果不理解就不要投票...... -1 @qauamrana 说什么。
【解决方案3】:

这会编译链接并使用 VS2008 和 gcc 4.5.2 运行:

template<class C> int Foo();

int Something(int i ){ return i; }

template<class C> int Bar() {
    return Something(  Foo<C>()  );
}

class MyClass{};
class FooClass{};

// Update:
// Declaration of a specialisation.
template<> int Foo<MyClass>();

int Zoo(){
    return Something(  Foo<MyClass>()  );
}

#include <iostream>

int main() {
    std::cout << Bar<MyClass>() << std::endl;
    std::cout << Zoo() << std::endl;
    std::cout << Bar<FooClass>() << std::endl;
    return 0;
}

// Definitions of specialisations.
template<> int Foo<MyClass>() { return 5; }
template<> int Foo<FooClass>() { return 6; }

输出是:

5
5
6

这样做的原因是,尽管只声明了模板函数Foo,但确实有两个必需的定义可供链接器使用。
在编译时,当编译器看到main 的定义时,它可以创建Bar 的特化,但不能创建Foo 的特化。在这种情况下,它只是创建对Foo&lt;MyClass&gt;()Foo&lt;FooClass&gt;() 的函数调用。
后来编译器找到了它编译的Foo 的特化,留在目标文件中,但当时没有做任何其他事情。当链接器运行时,它会找到所需的一切。

更新:
所以我不知道为什么这是非法的,但由于某种原因,它似乎可以编译和运行。

【讨论】:

  • 您的描述正是我想要的行为者。 OTOH,虽然它可以编译和运行,但它是非法代码:permalink.gmane.org/gmane.comp.compilers.llvm.bugs/10472 我认为 GCC 和 clang 都会拒绝它。
  • @BCS:我认为这段代码合法的,因为编译 main 时没有定义Foo。如果有,gcc 4.5.2 会返回一个简单的多重定义错误。
  • 如果 Bar 没有被模板化并且 Foo 调用直接指定类型,那么这会产生一个错误消息(即它是非法的)。除非我遗漏了什么,否则就合法性(即两者都是非法的)而言,这不应该有所作为,因为无论哪种方式 Foo 调用都应该在 Foo 定义之前处理。
猜你喜欢
  • 1970-01-01
  • 2015-11-13
  • 1970-01-01
  • 2015-03-26
  • 1970-01-01
  • 2011-02-28
  • 1970-01-01
  • 2013-04-27
  • 2019-03-30
相关资源
最近更新 更多