112

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

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
}

正如我所说,我知道这是无效的,所以我的问题是为什么不是?允许这样做会造成任何重大的语法漏洞吗?是否存在不需要此功能的实例(推断类型会导致问题)?我只是想了解允许对函数进行模板推断的逻辑,但不适用于构造适当的类。

4

12 回答 12

49

我认为它是无效的,因为构造函数并不总是类的唯一入口点(我说的是复制构造函数和 operator=)。因此,假设您正在像这样使用您的课程:

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);

接受的论文

于 2009-06-12T01:18:02.247 回答
27

由于其他人提出的原因,您不能做您要求的事情,但您可以这样做:

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

出于所有意图和目的,这与您要求的相同。如果你喜欢封装,你可以使 make_variable 成为一个静态成员函数。这就是人们所说的命名构造函数。所以它不仅做你想做的事,而且几乎被称为你想做的事:编译器从(命名的)构造函数推断模板参数。

注意:当您编写类似的东西时,任何合理的编译器都会优化掉临时对象

auto v = make_variable(instance);
于 2011-07-28T15:36:24.187 回答
23

在 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;

在这里,正如我在评论中指出的那样,没有理由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-which 的类型MyClass<string>(而不是不存在的类型MyClass)复制构造的。因此,在pm推断出 的类型时,有足够的信息可以知道 的模板类型m以及 的模板类型pmstring

此外,以下总是会 引发编译错误

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

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

MyClass(const MyClass&);

这里,copy-constructor 参数的模板类型与整个类的模板类型匹配;即当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 Pitiş 的答案

Pitiş 给出了一个演绎Variable<int>和的例子Variable<double>,然后说:

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

正如前面的例子中提到的,Variable它本身并不是一个类型名,尽管新特性使它在语法上看起来像一个类型名。

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

这类似于询问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>> ?

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

试一下代码,我们可以看到复制构造函数被选中了。扩展示例

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

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

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

于 2016-08-22T19:28:14.590 回答
12

仍然缺失:它使以下代码非常含糊:

int main()
{
    int num = 2;
    Variable var(num);  // If equivalent to Variable<int> var(num),
    Variable var2(var); //Variable<int> or Variable<Variable<int>> ?
}
于 2009-06-12T15:01:52.017 回答
9

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

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);

编译器应该做什么?使用泛型Variable类定义推导出它是Variable,然后发现Variable没有提供一个参数构造函数?

于 2009-06-12T07:50:40.633 回答
6

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

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

请参阅:http ://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3602.html和http://www.open-std.org/jtc1/sc22/wg21/docs/论文/2015/p0091r0.html

于 2013-04-11T00:22:44.327 回答
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);
}
于 2009-06-12T01:46:50.447 回答
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>
}
于 2009-06-12T11:36:09.157 回答
0

你是对的,编译器很容易猜到,但据我所知,它不在标准或 C++0x 中,所以在编译器供应商添加此功能之前,你必须至少再等 10 年(ISO 标准固定周转率)

于 2009-06-12T00:01:48.030 回答
-1

让我们参考一个大家都应该熟悉的类——std::vector来看看这个问题。

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

vector <int> v;

在这种情况下,显然无法进行推理。

第二个常见用途是创建一个预先确定大小的向量:

vector <string> v(100);

在这里,如果使用推理:

vector v(100);

我们得到一个整数向量,而不是字符串,大概它没有大小!

最后,考虑采用多个参数的构造函数 - 使用“推理”:

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

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

对于像向量这样简单的类的所有这些问题,很容易看出为什么不使用推理。

于 2009-06-12T08:11:05.280 回答
-2

使 ctor 成为模板,Variable 只能有一种形式,但可以有多种 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;         
}

看?我们不能有多个 Variable::data 成员。

于 2009-06-12T00:12:30.453 回答
-2

有关这方面的更多信息,请参阅C++ 模板参数推导

于 2009-06-12T00:14:22.820 回答