今天我的问题非常简单:为什么编译器不能从类构造函数中推断模板参数,就像它可以从函数参数那样做?例如,为什么以下代码无效:
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
}
正如我所说,我明白这是无效的,所以我的问题是为什么不是吗?允许这会产生任何重大的句法漏洞吗?是否存在不希望使用此功能的实例(推断类型会导致问题)?我只是想了解允许函数模板推理的逻辑,但不适用于适当构造的类。
答案 0 :(得分:44)
我认为它无效,因为构造函数并不总是该类的唯一入口点(我在谈论复制构造函数和operator =)。所以假设你正在使用这样的课程:
MyClass m(string s);
MyClass *pm;
*pm = m;
我不确定解析器知道MyClass pm的模板类型是否如此明显;
不确定我所说的内容是否有意义但随意添加一些评论,这是一个有趣的问题。
可以接受的是,C ++ 17将从构造函数参数中进行类型推导。
示例:
std::pair p(2, 4.5);
std::tuple t(4, 3, 2.5);
答案 1 :(得分:24)
由于其他人已解决的原因,你不能按照你的要求去做,但你可以这样做:
template<typename T>
class Variable {
public: Variable(T d) {}
};
template<typename T>
Variable<T> make_variable(T instance) {
return Variable<T>(instance);
}
出于所有意图和目的,你要求的是同一件事。 如果你喜欢封装,你可以使make_variable成为静态成员函数。这就是人们称之为命名构造函数的东西。因此,它不仅可以满足您的需求,而且几乎可以满足您的需求:编译器正在推断(命名)构造函数中的模板参数。
注意:当您编写类似
的内容时,任何合理的编译器都会优化掉临时对象Variable<T> v = make_variable(instance);
答案 2 :(得分:18)
在2016年的开明时代,自从提出这个问题以及新问题即将来临之后,我们有了两个新的标准,关键是要知道支持C ++ 17标准的编译器将会compile your code as-is 强>
Here(由Olzhas Zhumabek编辑接受的答案)是详细说明标准相关变更的文件。
这个答案指出“复制构造函数和operator=
”不知道正确的模板特化。
这是无稽之谈,因为已知模板类型的标准复制构造函数和operator=
仅存在:
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
)。因此,在推断出pm
类型的位置, 有足够的信息来知道m
的模板类型,因此知道模板类型{ {1}},pm
。
此外,以下总是 raise a compile error:
string
这是因为复制构造函数的声明不模板化:
MyClass s(string("blah blah blah"));
MyClass i(3);
i = s;
这里,copy-constructor参数的template-type 匹配整个类的模板类型;即,当MyClass(const MyClass&);
被实例化时,MyClass<string>
被实例化,并且当MyClass<string>::MyClass(const MyClass<string>&);
被实例化时,MyClass<int>
被实例化。除非明确指定或声明了模板化构造函数,否则编译器没有理由实例化MyClass<int>::MyClass(const MyClass<int>&);
,这显然是不合适的。
Pitiş给出了推导MyClass<int>::MyClass(const MyClass<string>&);
和Variable<int>
的示例,然后声明:
我在代码中有两种不同类型(变量和变量)的相同类型名称(变量)。从我的主观角度来看,它几乎影响了代码的可读性。
如前面的示例中所述,Variable<double>
本身不是类型名称,即使新功能使其看起来像语法上一样。
Pitiş然后询问如果没有给出允许适当推断的构造函数会发生什么。答案是不允许推理,因为推理是由构造函数调用触发的。如果没有构造函数调用,则无推理。
这类似于询问此处推断出Variable
的版本:
foo
答案是,由于上述原因,此代码是非法的。
就我所知,这是对提议的功能提出合理关注的唯一答案。
示例是:
template <typename T> foo();
foo();
关键问题是,编译器是在这里选择类型推断的构造函数还是 copy 构造函数?
尝试输出代码,我们可以看到选择了复制构造函数。 To expand on the example:
Variable var(num); // If equivalent to Variable<int> var(num),
Variable var2(var); // Variable<int> or Variable<Variable<int>> ?
我不确定该提案和标准的新版本如何指明这一点;它似乎是由“演绎指南”决定的,这是一个我还不了解的新标准。
我也不确定为什么Variable var(num); // infering ctor
Variable var2(var); // copy ctor
Variable var3(move(var)); // move ctor
// Variable var4(Variable(num)); // compiler error
扣除是非法的;来自g ++的编译器错误似乎表明该语句被解析为函数声明。
答案 3 :(得分:11)
仍然缺失:它使以下代码非常模糊:
int main()
{
int num = 2;
Variable var(num); // If equivalent to Variable<int> var(num),
Variable var2(var); //Variable<int> or Variable<Variable<int>> ?
}
答案 4 :(得分:9)
假设编译器支持您的要求。那么这段代码是有效的:
Variable v1( 10); // Variable<int>
// Some code here
Variable v2( 20.4); // Variable<double>
现在,我在代码中有两个不同类型(变量和变量)的相同类型名称(变量)。从我的主观角度来看,它几乎影响了代码的可读性。在同一命名空间中为两种不同类型设置相同的类型名称对我来说是误导。
稍后更新: 另一件需要考虑的事情:部分(或完整)模板专业化。
如果我专门使用Variable并且没有像你期望的那样提供构造函数怎么办?
所以我会:
template<>
class Variable<int>
{
// Provide default constructor only.
};
然后我有了代码:
Variable v( 10);
编译器应该怎么做?使用泛型变量类定义来推断它是变量,然后发现变量没有提供一个参数构造函数?
答案 5 :(得分:6)
C ++ 03和C ++ 11标准不允许从传递给constuructor的参数中扣除模板参数。
但是有一个关于“构造函数的模板参数扣除”的提议,所以你可能会很快得到你想要的东西。 编辑:的确,此功能已经在C ++ 17中得到确认。
请参阅:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3602.html和http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0091r0.html
答案 6 :(得分:2)
许多类不依赖于构造函数参数。只有少数类只有一个构造函数,并根据此构造函数的类型进行参数化。
如果您确实需要模板推理,请使用辅助函数:
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);
}
答案 7 :(得分:1)
类型的推导仅限于当前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>
}
答案 8 :(得分:0)
你是对的,编译器很容易猜到,但据我所知,它不在标准或C ++ 0x中,所以你必须等待至少10年(ISO标准修复转换率)才能在编译提供者之前添加此功能
答案 9 :(得分:-1)
让我们看一下这个问题,参考一个每个人都应该熟悉的类--std :: vector。
首先,vector的一个非常常见的用法是使用不带参数的构造函数:
vector <int> v;
在这种情况下,显然不能进行推理。
第二个常见用途是创建预先调整大小的矢量:
vector <string> v(100);
在这里,如果使用推理:
vector v(100);
我们得到一个整数的向量,而不是字符串,并且可能它没有大小!
最后,考虑采用多个参数的构造函数 - 使用“推理”:
vector v( 100, foobar() ); // foobar is some class
应该使用哪个参数进行推理?我们需要某种方式告诉编译器它应该是第二个。
对于像vector这样简单的类的所有这些问题,很容易看出为什么不使用推理。
答案 10 :(得分:-2)
将actor设为模板,变量只能有一个表单但是各个扇区:
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;
}
请参阅?我们不能有多个Variable :: data成员。
答案 11 :(得分:-2)
有关详细信息,请参阅The C++ Template Argument Deduction。