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

GRB*_*GRB 101 c++ parameters templates inference

今天我的问题很简单:为什么编译器不能从类构造函数中推断模板参数,就像它可以从函数参数那样做?例如,为什么以下代码无效:

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
}
Run Code Online (Sandbox Code Playgroud)

正如我所说,我明白这是无效的,所以我的问题是为什么不是呢?允许这会产生任何重大的句法漏洞吗?是否存在不希望使用此功能的实例(推断类型会导致问题)?我只是想了解允许函数的模板推理背后的逻辑,但不适用于适当构造的类.

Dra*_*kar 45

我认为这是无效的,因为构造函数并不总是类的唯一入口点(我在谈论复制构造函数和operator =).所以假设你正在使用这样的类:

MyClass m(string s);
MyClass *pm;
*pm = m;
Run Code Online (Sandbox Code Playgroud)

我不确定解析器是否会明白哪个模板类型是MyClass pm;

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

C++ 17

可以接受的是,C++ 17将从构造函数参数中进行类型推导.

例子:

std::pair p(2, 4.5);
std::tuple t(4, 3, 2.5);
Run Code Online (Sandbox Code Playgroud)

接受的纸.

  • 这实际上是一个我从未考虑过的好点.我没有看到任何方式指针必须是特定于类型的事实(即它必须是MyClass <string>*pm).如果是这种情况,那么你最终要做的就是避免在实例化时指定类型; 一些额外工作的角色(只有当对象是在堆栈上制作而不是堆,如上所述).我一直怀疑类推断可能会打开一个语法的蠕虫,我想这可能是它. (8认同)
  • 我不太清楚如何允许构造函数的模板参数推断允许在没有*构造函数调用的情况下允许非专用声明*,就像在第二行中一样.即,这里的`MyClass*pm`是无效的,因为函数声明`template <typename T> void foo();`如果没有显式特化,就无法调用. (2认同)
  • @KyleStrand是的,通过说'类模板参数不能从它们的构造函数推导出来因为_ [不使用任何构造函数的例子] _',这个答案是完全无关紧要的.我真的不敢相信它已被接受,达到了+29,花了6年的时间让某人注意到这个明显的问题,而且没有一次downvote坐了7年.在他们阅读时没有其他人的想法,或者......? (2认同)

小智 26

由于其他人已经解决的原因,你不能按照你的要求去做,但你可以这样做:

template<typename T>
class Variable {
    public: Variable(T d) {}
};
template<typename T>
Variable<T> make_variable(T instance) {
  return Variable<T>(instance);
}
Run Code Online (Sandbox Code Playgroud)

出于所有意图和目的,你要求的是同一件事.如果你喜欢封装,你可以使make_variable成为静态成员函数.这就是人们称之为命名构造函数的东西.因此,它不仅可以满足您的需求,而且几乎可以满足您的需求:编译器从(命名)构造函数中推断出模板参数.

注意:任何合理的编译器都会在你写这样的东西时优化掉临时对象

Variable<T> v = make_variable(instance);
Run Code Online (Sandbox Code Playgroud)

  • 想要指出在这种情况下创建函数静态成员并不是特别有用,因为为此你必须指定一个类的模板参数来调用它,所以没有必要推断它. (6认同)
  • 在C++ 11中甚至更好,你可以做`auto v = make_variable(instance)`所以你实际上不需要指定类型 (3认同)
  • 是的,在将 make 函数声明为 `static` 成员的想法上哈哈大笑……想一想。除此之外:免费的 make 函数确实是 _the_ 解决方案,但它是很多多余的样板,当你输入它时,你只知道你不应该这样做,因为编译器可以访问你重复的所有信息。 .. 谢天谢地,C++17 规范了这一点。 (2认同)

Kyl*_*and 20

在2016年的开明时代,自从提出这个问题以来,我们已经提出了两个新标准,而新的标准即将到来,重要的是要知道支持C++ 17标准的编译器将按原样编译您的代码..

C++中类模板的模板参数推导17

这里(由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;
Run Code Online (Sandbox Code Playgroud)

在这里,我在评论中指出,没有任何理由MyClass *pm不符合或不推论的新形式的法律声明:MyClass 不是一个类型(这是一个模板),所以它没有任何意义申报的指针类型MyClass.以下是修复示例的一种可能方法:

MyClass m(string("blah blah blah"));
decltype(m) *pm;               // uses type inference!
*pm = m;
Run Code Online (Sandbox Code Playgroud)

这里,pm已经正确的类型,所以推断是微不足道的.而且,在调用copy-constructor时不可能意外地混合类型:

MyClass m(string("blah blah blah"));
auto pm = &(MyClass(m));
Run Code Online (Sandbox Code Playgroud)

这里,pm将是一个指向副本的指针m.这里MyClass是从m-which类型MyClass<string>(而不是不存在类型MyClass)复制构造的.因此,在点pm的类型推断,存在足够的信息来知道模板型的m,因此,模板型的pm,是string.

此外,以下将始终 引发编译错误:

MyClass s(string("blah blah blah"));
MyClass i(3);
i = s;
Run Code Online (Sandbox Code Playgroud)

这是因为复制构造函数的声明不是模板化的:

MyClass(const MyClass&);
Run Code Online (Sandbox Code Playgroud)

这里,copy-constructor参数的template-type 匹配整个类的模板类型; 即,当MyClass<string>实例化时,MyClass<string>::MyClass(const MyClass<string>&);用它实例化,并且当MyClass<int>实例化时,MyClass<int>::MyClass(const MyClass<int>&);实例化.除非明确指定或声明了模板化构造函数,否则编译器没有理由进行实例化MyClass<int>::MyClass(const MyClass<string>&);,这显然是不合适的.

CătălinPitiş的答案

Pitiş给出了一个例子推导Variable<int>Variable<double>,然后说:

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

如前面的示例所述,Variable它本身不是类型名称,即使新功能使其在语法上看起来像一个.

然后Pitiş询问如果没有给出允许适当推断的构造函数会发生什么.答案是不允许推理,因为推理是由构造函数调用触发的.没有构造函数调用,就没有推论.

这类似于询问foo此处推断的版本:

template <typename T> foo();
foo();
Run Code Online (Sandbox Code Playgroud)

答案是,由于上述原因,此代码是非法的.

MSalter的回答

据我所知,这是对提出的功能提出合理关注的唯一答案.

例子是:

Variable var(num);  // If equivalent to Variable<int> var(num),
Variable var2(var); // Variable<int> or Variable<Variable<int>> ?
Run Code Online (Sandbox Code Playgroud)

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

尝试编写代码,我们可以看到复制构造函数被选中.要扩展示例:

Variable var(num);          // infering ctor
Variable var2(var);         // copy ctor
Variable var3(move(var));   // move ctor
// Variable var4(Variable(num));     // compiler error
Run Code Online (Sandbox Code Playgroud)

我不确定该提案和该标准的新版本如何具体说明; 它似乎是由"演绎指南"决定的,这是我还不了解的一个新的标准.

我也不确定为什么var4扣除是非法的; 来自g ++的编译器错误似乎表明该语句被解析为函数声明.


MSa*_*ers 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>> ?
}
Run Code Online (Sandbox Code Playgroud)


Căt*_*tiș 9

假设编译器支持您的要求.那么这段代码是有效的:

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

// Some code here

Variable v2( 20.4); // Variable<double>
Run Code Online (Sandbox Code Playgroud)

现在,我在代码中有两个不同类型(变量和变量)的相同类型名称(变量).从我的主观角度来看,它几乎影响了代码的可读性.在同一命名空间中为两个不同类型设置相同的类型名称对我来说是误导性的.

稍后更新: 要考虑的另一件事:部分(或完整)模板专业化.

如果我专门使用Variable并且没有像你期望的那样提供构造函数怎么办?

所以我会:

template<>
class Variable<int>
{
// Provide default constructor only.
};
Run Code Online (Sandbox Code Playgroud)

然后我有代码:

Variable v( 10);
Run Code Online (Sandbox Code Playgroud)

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

  • @ jpinto3912 - 你错过了这一点.编译器必须实例化所有可能的Variable <T>以检查ANY ctor Variable <T> :: Variable是否提供了模糊的ctor.摆脱歧义不是问题 - 简单地实例化变量<double>自己,如果这是你想要的.它首先发现模棱两可使其变得不可能. (4认同)

Che*_*etS 6

C++ 03和C++ 11标准不允许从传递给constuructor的参数中扣除模板参数.

但是有一个建议"建筑师的模板参数扣除",所以你可能很快得到你要求的东西.编辑:事实上,这个功能已经被C++确认17.

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