为什么在函数参数类型中使用模板参数包作为模板参数列表不能被显式指定

时间:2022-05-06 16:33:03

I have the following piece of code:

我有以下一段代码:

template <typename, typename>
struct AAA{};

template<typename ...Args>
void f(AAA<Args...> *) {}

int main() {
    f<int, int>(nullptr);
}

This code results in a compile error. When compiling using g++ -std=c++1z the error shows as follows:

这段代码会导致编译错误。当使用g+ -std=c++1z编译时,错误如下:

prog.cc: In function 'int main()':
prog.cc:8:24: error: no matching function for call to 'f<int, int>(std::nullptr_t)'
     f<int, int>(nullptr);
                        ^
prog.cc:5:6: note: candidate: template<class ... Args> void f(AAA<Args ...>*)
 void f(AAA<Args...> *) {}
      ^
prog.cc:5:6: note:   template argument deduction/substitution failed:
prog.cc:8:24: note:   mismatched types 'AAA<Args ...>*' and 'std::nullptr_t'
     f<int, int>(nullptr);

Using clang++ -std=c++1z the error is:

使用clang++ -std=c++1z误差为:

prog.cc:8:5: error: no matching function for call to 'f'
    f<int, int>(nullptr);
    ^~~~~~~~~~~
prog.cc:5:6: note: candidate template ignored: could not match 'AAA<int, int, Args...> *' against 'nullptr_t'
void f(AAA<Args...> *) {}
     ^
1 error generated.

I am running those above in a MSYS2 MinGW-w64 environment. My GCC version is GCC 7.1.0 and my Clang version is 4.0.0; the standard library I use both in GCC and in Clang is the libstdc++ bundled with my GCC compiler.

我在MSYS2 MinGW-w64环境中运行上述操作。我的GCC版本是GCC 7.1.0,我的Clang版本是4.0.0;我在GCC和Clang中使用的标准库是与GCC编译器绑定的libstdc+。

In my opinion, the call to function template foo has its template parameter explicitly specified thus the template parameter pack and the function argument type should already be specified. However, the error diagnostics shown above seem to suggest that the exact type of function parameter and the nullptr argument does not match, which seems to be a issue only possible when function argument deduction occurs. So my question is, why does such error occur? Is it just a compiler bug, or does the C++ standard have some rules that indicate the original code is just ill-formed?

在我看来,对函数模板foo的调用具有显式指定的模板参数,因此应该已经指定了模板参数包和函数参数类型。但是,上面显示的错误诊断似乎表明函数参数的确切类型和nullptr参数不匹配,这似乎是只有在函数参数演绎发生时才可能出现的问题。我的问题是,为什么会出现这样的错误?它只是一个编译器错误,还是c++标准有一些规则表明原始代码只是格式不正确?

5 个解决方案

#1


25  

You may think the compiler should deduce the pack as int ,int, but the C++ standard explicitly requires the behavior you observed.

您可能认为编译器应该将包推断为int、int,但是c++标准明确要求您观察到的行为。

[temp.arg.explicit/9]

(temp.arg.explicit / 9)

Template argument deduction can extend the sequence of template arguments corresponding to a template parameter pack, even when the sequence contains explicitly specified template arguments. [ Example:

模板参数演绎可以扩展与模板参数包对应的模板参数序列,即使序列包含显式指定的模板参数。(例子:

template<class ... Types> void f(Types ... values);
void g() {
  f<int*, float*>(0, 0, 0);     // Types is deduced to the sequence int*, float*, int
}

— end example ]

——最后的例子)

The above means that even though some of the parameters were specified, deduction doesn't end. The parameter pack must always be expandable by argument deduction. It's as if the explicit arguments that were given are an instantiation of a template with a trailing parameter pack. When coupled with the following:

这意味着即使指定了一些参数,演绎也不会结束。参数包必须始终可以通过参数演绎进行扩展。这就好像给出的显式参数是带有末尾参数包的模板的实例化。与下列内容相结合:

[temp.arg.explicit/3]

(temp.arg.explicit / 3)

Trailing template arguments that can be deduced or obtained from default template-arguments may be omitted from the list of explicit template-arguments. A trailing template parameter pack not otherwise deduced will be deduced to an empty sequence of template arguments. ...

可以从默认模板参数中推断或获得的拖尾模板参数可以从显式模板参数列表中删除。一个未被推导的拖尾模板参数包将被推导为一个空的模板参数序列。

The compiler must match the un-deduced arguments to an empty pack. But it has nothing to deduce it from.

编译器必须将未推导的参数与空包匹配。但它并没有从中推断出来。

As such, your attempt to plug Args... into AAA cannot possibly match. Because the type sequence is two types with a trailing list (that the compiler cannot deduce as empty from nullptr). While AAA expects just two types.

因此,您试图插入Args…进入AAA不可能匹配。因为类型序列是带有跟踪列表的两种类型(编译器不能从nullptr推断为空)。AAA预计只有两种类型。

#2


15  

For you are using typename ...Args, the compiler doesn't know if int, int are all the template parameters in use or more are available by deducing the function argument. Therefore the function isn't instantiated yet and the compiler goes on trying to deduce all the other possible parameters for the parameter pack from the function arguments.

你用的是typename…Args,编译器不知道int, int是否是所有使用中的模板参数,或者通过演绎函数参数可以得到更多的参数。因此,函数还没有实例化,编译器继续尝试从函数参数中推导出参数包的所有其他可能的参数。

In other terms, this works:

换句话说,这是可行的:

f<int>(new AAA<int, int>);

Because you are saying that the first parameter is int, but the compiler expects a parameters list and goes on trying to find more and more parameters greedily from the function argument, then it instantiates the function template.

因为您说的第一个参数是int,但是编译器需要一个参数列表,然后继续贪婪地从函数参数中查找越来越多的参数,然后它实例化函数模板。

More or less the same happens in your case, but the compiler cannot deduce anything from nullptr_t for function arguments doesn't match. It expects a pointer to an A<...>, that is not the case when you pass in nullptr.
This would work instead:

在您的例子中,或多或少也会发生相同的情况,但是对于不匹配的函数参数,编译器不能从nullptr_t中推断出任何内容。它期望一个指向a的指针。>,在传入nullptr时不是这样的。这将工作:

template <typename, typename>
struct AAA{};

template<typename A, typename B>
void f(AAA<A, B> *) {}

int main() {
    f<int, int>(nullptr);
}

Because the compiler knows template arguments are two and you are providing all of them, there is nothing to deduce and the function can be instantiated. It also makes much more sense, for AAA accepts only two template parameters, so a parameter pack for f seems useless here.

因为编译器知道模板参数是2,并且您提供了所有的模板参数,所以没有什么可推断的,函数可以被实例化。它也更有意义,因为AAA只接受两个模板参数,所以f的参数包在这里似乎没有用处。

#3


12  

Just to add an easy solution:

只是为了添加一个简单的解决方案:

f<int, int>(nullptr); // doesn't work for the reasons explained by other answers
(*f<int, int>)(nullptr); // OK - does what you want

The latter forces the pack Args... to be {int, int}, and now the call itself isn't a call to a function template - it's just a call to a function pointer. We're calling a function that takes an AAA<int, int>*, and of course passing in nullptr is acceptable there.

后者迫使狼群靠近……要为{int, int},现在调用本身并不是对函数模板的调用——它只是对函数指针的调用。我们调用的函数取AAA *,当然传入nullptr是可以接受的。 ,>

For fun, you can also add arbitrarily many *s:

为了好玩,你还可以随意添加许多*s:

(*****f<int, int>)(nullptr); // still OK - does what you want

... but, you know... don't.

…但是,你知道…不喜欢。

#4


6  

I want to add another solution that invokes the {} notion

我想添加另一个调用{}概念的解决方案

template <typename, typename>
struct AAA{};

template<typename ...Args>
void f(AAA<Args...> *) {}

int main() {
    f<int, int>({});
}

When the argument is {}, deduction for the parameter is disabled (nondeduced context), so there will be no mismatch and the parameter initialization actually produces a null pointer aswell.

当参数为{}时,参数的演绎被禁用(非演绎上下文),因此不会出现不匹配,参数初始化实际上也会产生一个空指针。

#5


4  

Great answer by @skypjack.

伟大的@skypjack回答。

You need to help the compiler in deducing the function argument:

你需要帮助编译器推导函数参数:

AAA<int, int> *a = nullptr;
f<int, int>(a); //works

f<int, int>( (AAA<int, int> *)nullptr );  //even this will work.

Fundamentally, nullptr represents a "no object" which can be assigned to any pointer type.

从根本上说,nullptr表示一个“无对象”,可以分配给任何指针类型。

#1


25  

You may think the compiler should deduce the pack as int ,int, but the C++ standard explicitly requires the behavior you observed.

您可能认为编译器应该将包推断为int、int,但是c++标准明确要求您观察到的行为。

[temp.arg.explicit/9]

(temp.arg.explicit / 9)

Template argument deduction can extend the sequence of template arguments corresponding to a template parameter pack, even when the sequence contains explicitly specified template arguments. [ Example:

模板参数演绎可以扩展与模板参数包对应的模板参数序列,即使序列包含显式指定的模板参数。(例子:

template<class ... Types> void f(Types ... values);
void g() {
  f<int*, float*>(0, 0, 0);     // Types is deduced to the sequence int*, float*, int
}

— end example ]

——最后的例子)

The above means that even though some of the parameters were specified, deduction doesn't end. The parameter pack must always be expandable by argument deduction. It's as if the explicit arguments that were given are an instantiation of a template with a trailing parameter pack. When coupled with the following:

这意味着即使指定了一些参数,演绎也不会结束。参数包必须始终可以通过参数演绎进行扩展。这就好像给出的显式参数是带有末尾参数包的模板的实例化。与下列内容相结合:

[temp.arg.explicit/3]

(temp.arg.explicit / 3)

Trailing template arguments that can be deduced or obtained from default template-arguments may be omitted from the list of explicit template-arguments. A trailing template parameter pack not otherwise deduced will be deduced to an empty sequence of template arguments. ...

可以从默认模板参数中推断或获得的拖尾模板参数可以从显式模板参数列表中删除。一个未被推导的拖尾模板参数包将被推导为一个空的模板参数序列。

The compiler must match the un-deduced arguments to an empty pack. But it has nothing to deduce it from.

编译器必须将未推导的参数与空包匹配。但它并没有从中推断出来。

As such, your attempt to plug Args... into AAA cannot possibly match. Because the type sequence is two types with a trailing list (that the compiler cannot deduce as empty from nullptr). While AAA expects just two types.

因此,您试图插入Args…进入AAA不可能匹配。因为类型序列是带有跟踪列表的两种类型(编译器不能从nullptr推断为空)。AAA预计只有两种类型。

#2


15  

For you are using typename ...Args, the compiler doesn't know if int, int are all the template parameters in use or more are available by deducing the function argument. Therefore the function isn't instantiated yet and the compiler goes on trying to deduce all the other possible parameters for the parameter pack from the function arguments.

你用的是typename…Args,编译器不知道int, int是否是所有使用中的模板参数,或者通过演绎函数参数可以得到更多的参数。因此,函数还没有实例化,编译器继续尝试从函数参数中推导出参数包的所有其他可能的参数。

In other terms, this works:

换句话说,这是可行的:

f<int>(new AAA<int, int>);

Because you are saying that the first parameter is int, but the compiler expects a parameters list and goes on trying to find more and more parameters greedily from the function argument, then it instantiates the function template.

因为您说的第一个参数是int,但是编译器需要一个参数列表,然后继续贪婪地从函数参数中查找越来越多的参数,然后它实例化函数模板。

More or less the same happens in your case, but the compiler cannot deduce anything from nullptr_t for function arguments doesn't match. It expects a pointer to an A<...>, that is not the case when you pass in nullptr.
This would work instead:

在您的例子中,或多或少也会发生相同的情况,但是对于不匹配的函数参数,编译器不能从nullptr_t中推断出任何内容。它期望一个指向a的指针。>,在传入nullptr时不是这样的。这将工作:

template <typename, typename>
struct AAA{};

template<typename A, typename B>
void f(AAA<A, B> *) {}

int main() {
    f<int, int>(nullptr);
}

Because the compiler knows template arguments are two and you are providing all of them, there is nothing to deduce and the function can be instantiated. It also makes much more sense, for AAA accepts only two template parameters, so a parameter pack for f seems useless here.

因为编译器知道模板参数是2,并且您提供了所有的模板参数,所以没有什么可推断的,函数可以被实例化。它也更有意义,因为AAA只接受两个模板参数,所以f的参数包在这里似乎没有用处。

#3


12  

Just to add an easy solution:

只是为了添加一个简单的解决方案:

f<int, int>(nullptr); // doesn't work for the reasons explained by other answers
(*f<int, int>)(nullptr); // OK - does what you want

The latter forces the pack Args... to be {int, int}, and now the call itself isn't a call to a function template - it's just a call to a function pointer. We're calling a function that takes an AAA<int, int>*, and of course passing in nullptr is acceptable there.

后者迫使狼群靠近……要为{int, int},现在调用本身并不是对函数模板的调用——它只是对函数指针的调用。我们调用的函数取AAA *,当然传入nullptr是可以接受的。 ,>

For fun, you can also add arbitrarily many *s:

为了好玩,你还可以随意添加许多*s:

(*****f<int, int>)(nullptr); // still OK - does what you want

... but, you know... don't.

…但是,你知道…不喜欢。

#4


6  

I want to add another solution that invokes the {} notion

我想添加另一个调用{}概念的解决方案

template <typename, typename>
struct AAA{};

template<typename ...Args>
void f(AAA<Args...> *) {}

int main() {
    f<int, int>({});
}

When the argument is {}, deduction for the parameter is disabled (nondeduced context), so there will be no mismatch and the parameter initialization actually produces a null pointer aswell.

当参数为{}时,参数的演绎被禁用(非演绎上下文),因此不会出现不匹配,参数初始化实际上也会产生一个空指针。

#5


4  

Great answer by @skypjack.

伟大的@skypjack回答。

You need to help the compiler in deducing the function argument:

你需要帮助编译器推导函数参数:

AAA<int, int> *a = nullptr;
f<int, int>(a); //works

f<int, int>( (AAA<int, int> *)nullptr );  //even this will work.

Fundamentally, nullptr represents a "no object" which can be assigned to any pointer type.

从根本上说,nullptr表示一个“无对象”,可以分配给任何指针类型。