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

发布于 2024-07-23 13:57:53 字数 507 浏览 6 评论 0原文

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

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
}

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

my question today is pretty simple: why can't the compiler infer template parameters from class constructors, much as it can do from function parameters? For example, why couldn't the following code be valid:

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
}

As I say, I understand that this isn't valid, so my question is why isn't it? Would allowing this create any major syntactic holes? Is there an instance where one wouldn't want this functionality (where inferring a type would cause issues)? I'm just trying to understand the logic behind allowing template inference for functions, yet not for suitably-constructed classes.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

凉薄对峙 2024-07-30 13:57:53

我认为它是无效的,因为构造函数并不总是类的唯一入口点(我说的是复制构造函数和运算符=)。所以假设您像这样使用您的类:

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

已接受的论文

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;

I am not sure if it would be so obvious for the parser to know what template type is the MyClass pm;

Not sure if what I said make sense but feel free to add some comment, that's an interesting question.

C++ 17

It is accepted that C++17 will have type deduction from constructor arguments.

Examples:

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

Accepted paper.

森林很绿却致人迷途 2024-07-30 13:57:53

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

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

You can't do what you ask for reasons other people have addressed, but you can do this:

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

which for all intent and purposes is the same thing you ask for.
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.

NB: any reasonable compiler will optimize away the temporary object when you write something like

auto v = make_variable(instance);
多孤肩上扛 2024-07-30 13:57:53

在 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 复制构造的,其类型为 MyClass(并且不是不存在的类型MyClass)。 因此,在推断 pm 的类型时,有足够的信息来了解 m 的模板类型,因此pm 的模板类型是 string

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

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

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

MyClass(const MyClass&);

这里,复制构造函数参数的模板类型与整个类的模板类型相匹配; 即,当实例化 MyClass 时,MyClass::MyClass(const MyClass&); 会随之实例化,并且当 >MyClass 被实例化,MyClass::MyClass(const MyClass&); 被实例化。 除非显式指定或声明模板化构造函数,否则编译器没有理由实例化 MyClass::MyClass(const MyClass&);,这显然是不当。

Cătălin Pitiş Pitiş 的回答

给出了一个推导 VariableVariable 的例子,然后指出:

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

正如前面的示例中所指出的,Variable 本身不是类型名称,尽管新功能使其在语法上看起来像一个类型名称。

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

这类似于询问此处推导的 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>> ?

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

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

int num = 3;
Variable var(num);            // infering ctor
Variable var2(var);           // copy ctor
Variable var3(move(var));     // move ctor
Variable var4{Variable(num)}; // infering ctor
// Variable var4(Variable(num));     // illegal

我不确定该提案和新版本如何标准对此进行了规定; 它似乎是由“演绎指南”决定的,这是我还不明白的一些新的标准术语。

由于“最令人烦恼的解析”(该语句被解析为函数声明),var4 推导是非法的。 我不完全确定为什么,因为对我来说这看起来不像函数声明的有效语法。

In the enlightened age of 2016, with two new standards under our belt since this question was asked and a new one just around the corner, the crucial thing to know is that compilers supporting the C++17 standard will compile your code as-is.

Template-argument deduction for class templates in C++17

Here (courtesy of an edit by Olzhas Zhumabek of the accepted answer) is the paper detailing the relevant changes to the standard.

Addressing concerns from other answers

The current top-rated answer

This answer points out that "copy constructor and operator=" wouldn't know the correct template specializations.

This is nonsense, because the standard copy-constructor and operator= only exist for a known 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;

Here, as I noted in the comments, there is no reason for MyClass *pm to be a legal declaration with or without the new form of inference: MyClass is not a type (it's a template), so it doesn't make sense to declare a pointer of type MyClass. Here's one possible way to fix the example:

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

Here, pm is already of the correct type, and so the inference is trivial. Moreover, it's impossible to accidentally mix types when calling the copy-constructor:

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

Here, pm will be a pointer to a copy of m. Here, MyClass is being copy-constructed from m—which is of type MyClass<string> (and not of the nonexistent type MyClass). Thus, at the point where pm's type is inferred, there is sufficient information to know that the template-type of m, and therefore the template-type of pm, is string.

Moreover, the following will always raise a compile error:

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

This is because the declaration of the copy constructor is not templated:

MyClass(const MyClass&);

Here, the copy-constructor argument's template-type matches the template-type of the class overall; i.e., when MyClass<string> is instantiated, MyClass<string>::MyClass(const MyClass<string>&); is instantiated with it, and when MyClass<int> is instantiated, MyClass<int>::MyClass(const MyClass<int>&); is instantiated. Unless it is explicitly specified or a templatized constructor is declared, there is no reason for the compiler to instantiate MyClass<int>::MyClass(const MyClass<string>&);, which would obviously be inappropriate.

The answer by Cătălin Pitiș

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

I have the same type name (Variable) in the code for two different types (Variable and Variable). From my subjective point of view, it affects the readability of the code pretty much.

As noted in the previous example, Variable itself is not a type name, even though the new feature makes it look like one syntactically.

Pitiș then asks what would happen if no constructor is given that would permit the appropriate inference. The answer is that no inference is permitted, because the inference is triggered by the constructor call. Without a constructor-call, there is no inference.

This is similar to asking what version of foo is deduced here:

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

The answer is that this code is illegal, for the reason stated.

MSalter's answer

This is, as far as I can tell, the only answer to bring up a legitimate concern about the proposed feature.

The example is:

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

The key question is, does the compiler select the type-inferred constructor here or the copy constructor?

Trying the code out, we can see that the copy constructor is selected. To expand on the example:

int num = 3;
Variable var(num);            // infering ctor
Variable var2(var);           // copy ctor
Variable var3(move(var));     // move ctor
Variable var4{Variable(num)}; // infering ctor
// Variable var4(Variable(num));     // illegal

I am not sure how the proposal and the new version of the standard specify this; it appears to be determined by "deduction guides," which are a new bit of standardese that I don't yet understand.

The var4 deduction is illegal due to the "most vexing parse" (the statement is parsed as a function declaration). I'm not totally sure why, because that doesn't look like a valid syntax for a function declaration to me.

东北女汉子 2024-07-30 13:57:53

仍然缺失:它使得下面的代码相当模糊:

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

Still missing: It makes the following code quite ambiguous:

int main()
{
    int num = 2;
    Variable var(num);  // If equivalent to Variable<int> var(num),
    Variable var2(var); //Variable<int> or Variable<Variable<int>> ?
}
笛声青案梦长安 2024-07-30 13:57:53

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

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

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

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>

Now, I have the same type name (Variable) in the code for two different types (Variable and Variable). From my subjective point of view, it affects the readability of the code pretty much. Having same type name for two different types in the same namespace looks misleading to me.

Later update:
Another thing to consider: partial (or full) template specialization.

What if I specialize Variable and provide no constructor like you expect?

So I would have:

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

Then I have the code:

Variable v( 10);

What should the compiler do? Use generic Variable class definition to deduce that it is Variable, then discover that Variable doesn't provide one parameter constructor?

不回头走下去 2024-07-30 13:57:53

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

The C++03 and the C++11 standard does not allow for template argument deduction from the parameters passed to the constuructor.

But there is a proposal for "Template parameter deduction for constructors" so you may get what you are asking for soon. Edit: indeed, this feature has been confirmed for C++17.

See: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3602.html and http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0091r0.html

时光沙漏 2024-07-30 13:57:53

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

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

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

A lot of classes don't depend on constructor parameters. There are only a few classes that have only one constructor, and parameterize based on this constructor's type(s).

If you really need template inference, use a helper function:

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);
}
倾城泪 2024-07-30 13:57:53

类型推导仅限于当前 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>
}

Deduction of types is limited to template functions in current C++, but it's long been realised that type deduction in other contexts would be very useful. Hence C++0x's auto.

While exactly what you suggest won't be possible in C++0x, the following shows you can get pretty close:

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>
}
仙气飘飘 2024-07-30 13:57:53

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

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

好菇凉咱不稀罕他 2024-07-30 13:57:53

让我们参考每个人都应该熟悉的类 - std::vector 来看看这个问题。

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

vector <int> v;

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

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

vector <string> v(100);

这里,如果使用推理:

vector v(100);

我们得到一个整数向量,而不是字符串,并且可能它没有调整大小!

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

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

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

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

Let's look at the problem with reference to a class everyone should be familar with - std::vector.

Firstly, a very common use of vector is to use the constructor that takes no parameters:

vector <int> v;

In this case, obviously no inference can be performed.

A second common use is to create a pre-sized vector:

vector <string> v(100);

Here, if inference were used:

vector v(100);

we get a vector of ints, not strings, and presumably it isn't sized!

Lastly, consider constructors that take multiple parameters - with "inference":

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

Which parameter should be used for inference? We would need some way of telling the compiler that it should be the second one.

With all these problems for a class as simple as vector, it's easy to see why inference is not used.

沐歌 2024-07-30 13:57:53

将 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;         
}

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

Making the ctor a template the Variable can have only one form but various ctors:

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

See? We can not have multiple Variable::data members.

人生百味 2024-07-30 13:57:53

有关详细信息,请参阅C++ 模板参数推导

See The C++ Template Argument Deduction for more info on this.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文