为什么不从构造函数中推断模板参数?

我今天的问题很简单: 为什么编译器不能从类构造函数中推断出模板参数呢?例如,为什么下面的代码无效:

template <typename obj>
class Variable {
obj data;
public:
Variable(obj d) { data = d; }
};


int main() {
int num = 2;
Variable var(num); // would be equivalent to Variable<int> var(num),
return 0;          // but actually a compile error
}

正如我所说,我明白这是无效的,所以我的问题是 为什么,不是吗?允许这样做会造成任何重大的语法漏洞吗?有没有一个实例不需要这个功能(推断类型会导致问题) ?我只是试图理解允许函数进行模板推理背后的逻辑,而不是为构造合适的类进行模板推理。

20987 次浏览

You are right the compiler could easily guess, but it's not in the standard or C++0x as far as I know so you'll have to wait atleast 10 more years (ISO standards fixed turn around rate) before compiller providers add this feature

将 ctor 作为模板,变量只能有一个 表格,但是可以有多个 ctor:

class Variable {
obj data; // let the compiler guess
public:
template<typename obj>
Variable(obj d)
{
data = d;
}
};


int main()
{
int num = 2;
Variable var(num);  // Variable::data int?


float num2 = 2.0f;
Variable var2(num2);  // Variable::data float?
return 0;
}

看到了吗? 我们不能有多个变量: : 数据成员。

有关这方面的更多信息,请参见 C + + 模板参数演绎

I think it is not valid because the constructor isn't always the only point of entry of the class (I am talking about copy constructor and operator=). So suppose you are using your class like this :

MyClass m(string s);
MyClass *pm;
*pm = m;

我不确定对于解析器来说,知道 MyClass pm 是什么样的模板类型是否如此明显;

我不确定我说的是否有意义,但请随意添加一些评论,这是一个有趣的问题。

C + + 17

公认的是,C + + 17将从构造函数参数中进行类型推导。

例子:

std::pair p(2, 4.5);
std::tuple t(4, 3, 2.5);

已接纳文件。

很多类不依赖于构造函数参数。只有少数几个类只有一个构造函数,并且根据这个构造函数的类型进行参数化。

如果您确实需要模板推理,请使用 helper 函数:

template<typename obj>
class Variable
{
obj data;
public:
Variable(obj d)
: data(d)
{ }
};


template<typename obj>
inline Variable<obj> makeVariable(const obj& d)
{
return Variable<obj>(d);
}

Supposing that the compiler supports what you asked. Then this code is valid:

Variable v1( 10); // Variable<int>


// Some code here


Variable v2( 20.4); // Variable<double>

现在,我在代码中为两个不同的类型(变量和变量)提供了相同的类型名称(变量)。从我的主观角度来看,它非常影响代码的可读性。在同一命名空间中对两个不同类型使用相同的类型名称对我来说是一种误导。

稍后更新: 另一件需要考虑的事情是: 部分(或全部)模板专门化。

如果我专门的变量,并提供没有像您期望的构造函数?

所以我会:

template<>
class Variable<int>
{
// Provide default constructor only.
};

我有密码:

Variable v( 10);

编译器应该做什么?使用泛型变量类定义来推断它是变量,然后发现变量不提供一个参数构造函数?

让我们看看这个问题,它涉及到一个每个人都应该熟悉的类-std: : Vector。

首先,矢量的一个常见用法是使用不带参数的构造函数:

vector <int> v;

In this case, obviously no inference can be performed.

第二个常见用途是创建一个预设大小的矢量:

vector <string> v(100);

在这里,如果使用推论:

vector v(100);

我们得到的是整型向量,而不是字符串,而且大概它也不是大小!

最后,考虑一下接受多个参数的构造函数——使用“推理”:

vector v( 100, foobar() );      // foobar is some class

应该使用哪个参数进行推理?我们需要一些方法来告诉编译器它应该是第二个。

对于向量这样简单的类来说,有了所有这些问题,就很容易明白为什么不使用推理了。

类型推导仅限于当前 C + + 中的模板函数,但人们很早就意识到类型推导在其他上下文中非常有用。因此 C + + 0x 是 auto

虽然你的建议在 C + + 0x 中是不可能实现的,但是下面的例子表明你可以非常接近它:

template <class X>
Variable<typename std::remove_reference<X>::type> MakeVariable(X&& x)
{
// remove reference required for the case that x is an lvalue
return Variable<typename std::remove_reference<X>::type>(std::forward(x));
}


void test()
{
auto v = MakeVariable(2); // v is of type Variable<int>
}

仍然缺少: 它使得以下代码相当模糊:

int main()
{
int num = 2;
Variable var(num);  // If equivalent to Variable<int> var(num),
Variable var2(var); //Variable<int> or Variable<Variable<int>> ?
}

你不能因为别人说过的原因而做你要求做的事情,但是你可以这样做:

template<typename T>
class Variable {
public: Variable(T d) {}
};
template<typename T>
Variable<T> make_variable(T instance) {
return Variable<T>(instance);
}

无论出于何种目的,这都是你所要求的。 If you love encapsulation you can make make_variable a static member function. That's what people call named constructor. So not only does it do what you want, but it's almost called what you want: the compiler is infering the template parameter from the (named) constructor.

注意: 任何合理的编译器都会在您编写如下代码时优化掉临时对象

auto v = make_variable(instance);

C + + 03和 C + + 11标准不允许从传递给构造函数的参数中扣除模板参数。

但是有一个关于“构造函数的模板参数演绎”的建议,所以你可能很快就会得到你想要的东西。编辑: 实际上,这个特性已经在 C + + 17中得到确认。

参见: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3602.htmlhttp://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0091r0.html

在2016年的启蒙时代,随着这个问题被提出以来的两个新标准和即将出现的一个新标准的出现,我们需要知道的关键是 支持 C + + 17标准的编译器将按照原样编译代码

C + + 17中类模板的模板参数演绎

这里 (由 Olzhas Zhumabek 编辑的已接受的答案)是详细说明对标准的相关修改的文件。

解决来自其他答案的担忧

当前最流行的答案

这个答案指出“复制建构子和 operator=”不会知道正确的模板专门化。

This is nonsense, because the standard copy-constructor and operator= 只存在于 for a 已知 template type:

template <typename T>
class MyClass {
MyClass(const MyClass&) =default;
... etc...
};


// usage example modified from the answer
MyClass m(string("blah blah blah"));
MyClass *pm;   // WHAT IS THIS?
*pm = m;

在这里,正如我在评论中提到的,MyClass *pm没什么是一个合法的声明,不管有没有新的推理形式: MyClass 不是一种类型(它是一个模板) ,所以声明一个类型为 MyClass的指针是没有意义的。这里有一个可能的方法来修正这个例子:

MyClass m(string("blah blah blah"));
decltype(m) *pm;               // uses type inference!
*pm = m;

在这里,pm是正确类型的 已经,因此推断是琐碎的。此外,在调用拷贝构造函数时,不可能意外地使用 混音类型:

MyClass m(string("blah blah blah"));
auto pm = &(MyClass(m));

在这里,pm将是一个指向 m副本的指针。在这里,MyClass是从 mーー属于 MyClass<string>类型(以及不存在的 MyClass类型的 m0)复制构建的。因此,在推断出 pm的类型时,有足够的 m1信息来知道 m的模板类型,因此也就是 pm的模板类型是 string

此外,以下将 一直都是 引发编译错误:

MyClass s(string("blah blah blah"));
MyClass i(3);
i = s;

这是因为复制建构子的声明是以 没有为模板:

MyClass(const MyClass&);

在这里,复制构造函数参数的模板类型 火柴是整个类的模板类型; 也就是说,当 MyClass<string>被实例化时,MyClass<string>::MyClass(const MyClass<string>&);被实例化,当 MyClass<int>被实例化时,MyClass<int>::MyClass(const MyClass<int>&);被实例化。除非显式地指定它或者声明了模板化的构造函数,否则编译器没有理由实例化 MyClass<int>::MyClass(const MyClass<string>&);,这显然是不合适的。

C t lin Pitisomething 的回答

Pitiș gives an example deducing Variable<int> and Variable<double>, then states:

我有相同的类型名称(变量)在两个不同的类型(变量和变量)的代码。从我的主观角度来看,它非常影响代码的可读性。

正如前面的示例中指出的,Variable本身是 没有类型名,尽管新特性使它在语法上看起来像一个类型名。

然后,Pitisomething 询问,如果没有给出允许适当推理的构造函数,会发生什么情况。答案是不允许进行推理,因为推理是由 constructor call触发的。没有构造函数调用,就有 没有推理

这类似于在这里推导 foo的版本:

template <typename T> foo();
foo();

答案是,这个代码是非法的,原因如下。

MSalter 的回答

据我所知,这是对提议的特性提出合理担忧的唯一答案。

例如:

Variable var(num);  // If equivalent to Variable<int> var(num),
Variable var2(var); // Variable<int> or Variable<Variable<int>> ?

关键问题是,编译器是选择这里的 类型推断构造函数还是选择 copy构造函数?

尝试输出代码,我们可以看到复制建构子被选中了:

Variable var(num);          // infering ctor
Variable var2(var);         // copy ctor
Variable var3(move(var));   // move ctor
// Variable var4(Variable(num));     // compiler error

我不确定这个提案和新版本的标准是如何规定这一点的; 它似乎是由“演绎指南”决定的,这是一个我还不理解的新标准。

我也不确定为什么 var4演绎是非法的; g + + 的编译器错误似乎表明语句被解析为函数声明。