【问题标题】:Why don't I need template parameters in this particular case?为什么在这种特殊情况下我不需要模板参数?
【发布时间】:2017-03-09 22:39:00
【问题描述】:

我有这个代码:

struct Base {};

template<typename T>
struct Foo : Base {};

struct Bar {
    template<typename T> //           v--- What's happening here?
    Bar(T foo) : baz{std::make_unique<Foo>(foo)} {}

    std::unique_ptr<Base> baz;
};

令人惊讶的是,GCC 和 Clang 接受并编译它。好像推导出了Foo的模板参数,但是没有意义。即使没有采用模板模板参数的std::make_unique 重载,编译器怎么会接受呢? Live example

【问题讨论】:

  • Err... 尝试使用该构造函数创建一个 Bar 对象,如果可以的话。然后告诉我们编译器是否接受代码。
  • @WhiZTiM 它拒绝代码。我仍然觉得这两个编译器都没有抱怨模板参数类型不匹配真的很奇怪。

标签: c++ templates c++14


【解决方案1】:

在某些情况下,无论提供什么模板参数,模板总是无效的,但编译器无法解决这个问题,因为它没有能力尝试替换所有可能的模板集论据。根据标准([temp.res]/8):

如果没有有效的专业化可以 为模板生成,并且该模板未实例化,模板格式错误,无诊断 必填。

这意味着允许编译器很聪明并且证明没有有效的特化,并产生编译错误,但也允许它不够聪明,并且不产生编译错误。当然,一旦模板被实例化,那么编译器必须诊断错误。

使用没有模板参数的模板名称并不违法。在某些情况下,编译器可以推断出参数。例如:

template <class T>
void foo(T x);

int main() {
    void (*p)(int) = foo;  // p points to foo<int>
}

在您的代码中,事实证明您在无法推断模板参数的上下文中使用了Foo。如果编译器更聪明,他们就会明白这一点。但他们没有设法弄清楚这一事实并不意味着您的代码是正确的。

【讨论】:

  • 不错的代码sn-p,不知道这样的代码是有效的,模板参数可以在这样的上下文中推导出来。
【解决方案2】:

之所以可行,是因为 C++ 实际上不会创建模板函数,直到从代码中的某处调用它们。在这种情况下,由于您的代码从未尝试创建 Bar,因此您的模板 one-arg Bar 构造函数永远不会生成,因此编译器无需检查代码是否正确。

模板函数一经使用就由编译器生成,它们仅根据传入的类型生成。因此,如果您尝试像这样创建Bar

Foo<int> f;
Bar b = Bar(f);

编译器将尝试生成构造函数并失败并出现如下错误:

error C2955: 'Foo' : use of class template requires template argument list

因为它现在知道代码是错误的。

如果你想一想,这就是它的工作方式:由于 C++ 不允许你对模板类型施加约束,编译器必须尝试所有可能的模板类型组合来确定模板函数是否有语法错误。

【讨论】:

  • 编译器也会执行一些模板检查,但在这种情况下确实没有执行检查。规则其实挺复杂的,详细解释见C++ Templates: A complete guide
  • 是的,您可以通过 sfinae 模拟概念来约束任何模板参数。
  • @GuillaumeRacicot 这不允许您限制可用于实例化模板函数的类型。它只允许您在某些重载中创建语法错误,以便在重载解决方案中实例化的优先级较低。任何类型仍然可以用于任何模板类型,因此编译器仍然需要将任何类型视为可能的候选者。正如我所说,这使得在选择特定实例之前无法检查语法错误。
  • @DanielEisener 您确实是对的,即使使用 sfinae,您对依赖类型所做的操作在实例化之前都无关紧要。
猜你喜欢
  • 2018-09-08
  • 2017-07-15
  • 1970-01-01
  • 2020-11-29
  • 2021-11-16
  • 1970-01-01
  • 2013-01-25
  • 1970-01-01
  • 2016-08-08
相关资源
最近更新 更多