19

我知道,给定一个大括号初始值设定项,auto将推导出一个 类型std::initializer_list,而模板类型推导将失败:

auto var = { 1, 2, 3 };   // type deduced as std::initializer_list<int>

template<class T> void f(T parameter);

f({ 1, 2, 3 });          // doesn't compile; type deduction fails

我什至知道这在 C++11 标准中是在哪里指定的:14.8.2.5/5 bullet 5:

[如果程序有,这是一个非推导上下文] 一个函数参数,其关联参数是一个初始化列表 (8.5.4),但该参数没有 std::initializer_list 或对可能有 cv 限定的 std::initializer_list 的引用类型。[示例:

模板无效 g(T);

g({1,2,3}); // 错误:没有为 T 推导出参数

—<em>结束示例]

我不知道或不理解的是为什么存在这种类型推断行为的差异。C++14 CD 中的规范与 C++11 中的规范相同,因此标准化委员会可能不会将 C++11 行为视为缺陷。

有谁知道为什么auto要为大括号初始化器推导出一个类型,但不允许模板这样做?虽然“这可能是原因”形式的推测性解释很有趣,但我对那些知道标准为何如此编写的人的解释特别感兴趣。

4

3 回答 3

14

模板不做任何推论有两个重要原因(我记得在与负责人讨论中的两个)

  • 对未来语言扩展的担忧(您可以发明多种含义 - 如果我们想为大括号初始化列表函数参数引入完美转发呢?)

  • 大括号有时可以有效地初始化依赖的函数参数

template<typename T>
void assign(T &d, const T& s);
int main() {
  vector<int> v;
  assign(v, { 1, 2, 3 });
}

如果T将在右侧推导到 ,initializer_list<int>但在左侧推导到vector<int>,由于矛盾的论点推导,这将无法工作。

autoto的扣除initializer_list<T>是有争议的。有一个建议 C++-after-14 将其删除(并禁止使用{ }or进行初始化{a, b},并{a}推断出 的类型a)。

于 2013-07-12T19:37:55.037 回答
2

原因在N2640中有描述:

{}-list 不能针对普通类型参数进行推断T。例如:

template<class T> void count(T); // (1).
struct Dimensions { Dimensions(int, int); };
size_t count(Dimensions); // (2).
size_t n = count({1, 2}); // Calls (2); deduction doesn't
                          // succeed for (1).

另一个例子:

template<class T>
void inc(T, int); // (1)
template<class T>
void inc(std::initializer_list<T>, long); // (2)
inc({1, 2, 3}, 3); // Calls (2). (If deduction had succeeded
                   // for (1), (1) would have been called — a
                   // surprise.)

另一方面,能够推断出initializer_list<X>forT是有吸引力的,以允许:

auto x = { 1, 1, 2, 3, 5 };
f(x);
g(x);

自从 EWG 关于初始化列表的讨论一开始就被认为是可取的行为。

T与其为与 {}-list 匹配的参数类型(我们在本文的早期草图和草稿中采用的一个选项)提出一个巧妙的推导规则,我们现在更愿意使用“auto”变量的特殊情况来处理这个问题当初始值设定项是 {}-list 时的推导。即,对于使用“auto”类型说明符和 {}-list 初始值设定项声明的变量的特定情况,“auto”被推导出为函数f(initializer_list<T>)而不是函数f(T)

总而言之,问题在于,如果我们允许 {}-list 对普通类型参数进行推断T,那么带参数的函数T在重载解析期间将具有非常高的优先级,这可能会导致连线行为(如上面的示例)。

于 2018-07-22T14:49:47.523 回答
0

首先,正如您所说,它是“对“这可能是原因”形式的推测性解释”。

{1,2,3}不仅是std::initializer_list<int>而且还允许在没有构造函数的情况下初始化类型。例如:

#include <initializer_list>

struct x{
    int a,b,c;
};

void f(x){

}
int main() {
    f({1,2,3});
}

是正确的代码。为了证明它不是,initializer_list让我们看看下面的代码:

#include <initializer_list>

struct x{int a,b,c;};

void f(x){

}
int main() {
    auto il = {1, 2, 3};
    f(il);
}

错误是:

prog.cpp: In function ‘int main()’:
prog.cpp:10:9: error: could not convert ‘il’ from ‘std::initializer_list<int>’ to ‘x’

现在到问题“有什么区别?”

auto x = {1, 2, 3};代码中可以确定类型,因为编码器明确表示“它是什么类型并不重要”使用auto

而在功能模板的情况下,他可以确定他使用的是不同的类型。并且在模棱两可的情况下防止错误是很好的(它看起来不像 C++ 风格,通过)。

特别糟糕的是,如果有 1 个功能f(x),然后将其更改为模板 1。程序员将其用作x,在为其他类型添加新函数后,它会稍微更改为调用完全不同的函数。

于 2013-07-11T20:50:25.923 回答