variadic模板参数包扩展释放资格
假设我有一个可变参数函数模板,它将函数指针指向具有所述可变参数的函数。以下代码不能在 gcc (11.2) 下编译,但可以在 clang 和 msvc 下编译 (https://godbolt.org/ z/TWbEKWb9f)。
#include <type_traits>
void dummyFunc(const int);
template<typename... Args>
void callFunc(void(*)(Args...), Args&&...); // <- this one is problematic
// see https://stackoverflow.com/questions/67081700/variadic-template-qualifiers
template<typename... Args>
void callFunc2(void(*)(std::conditional_t<std::is_const_v<Args>, const Args, Args>...), Args&&...); // <- this one works
int main()
{
// fails on gcc, works on clang and msvc
callFunc<const int>(&dummyFunc, 2);
// this works
//callFunc(&dummyFunc, 2);
// this works as well
//callFunc2<const int>(&dummyFunc, 2);
}
将函数参数“Args...”显式指定为“const int”可防止 gcc 编译代码。显然,模板参数扩展在 gcc 下失去了 cv 限定符,而在 clang 和 msvc 下保留了它。错误消息是:
<source>: In function 'int main()':
<source>:15:24: error: no matching function for call to 'callFunc<const int>(void (*)(int), int)'
15 | callFunc<const int>(&dummyFunc, 2);
| ~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~
<source>:6:6: note: candidate: 'template<class ... Args> void callFunc(void (*)(Args ...), Args&& ...)'
6 | void callFunc(void(*)(Args...), Args&&...);
| ^~~~~~~~
<source>:6:6: note: template argument deduction/substitution failed:
<source>:15:24: note: types 'const int' and 'int' have incompatible cv-qualifiers
15 | callFunc<const int>(&dummyFunc, 2);
| ~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~
我通过利用类型特征使其在此处编译找到了“解决方案”(Variadic 模板限定符),但我要么必须使用这个方法,要么不使用这个方法,与编译器无关。我知道我不必显式指定模板参数,但我想了解为什么 gcc 无法编译代码。哪个编译器是“正确的”,即该代码是否应该编译(即,应该删除 cv 限定符)?
附带说明一下,如果我使用 const 引用,它也会在 gcc 下编译,并且模板参数会被正确识别为“const int&” (因为编译器绝对必须这样做)。我知道这样一个事实:如果模板参数是由编译器推导出来的并且未明确指定,则 cv 限定符将被删除。但在我看来,gcc 的行为是错误的,因为我明确指出了要使用的类型。但是,我不太了解该标准,无法判断 gcc 或其他两个编译器是否不遵循该标准。它似乎与可变参数模板有关,而不是模板推导本身,因为没有可变参数模板的版本在 gcc 下工作(https: //godbolt.org/z/qn8a5bh5E):
void dummyFunc(const int);
template<typename Arg>
void callFunc(void(*)(Arg), Arg&&);
int main()
{
callFunc<const int>(dummyFunc, 2);
}
另外,为什么自动模板类型推导甚至在第一种(有问题的)情况下也能工作?由于函数指针,'Args...' 应该被推导为 'const int',由于第二个参数,'Args...' 应该被推导为 'int'。我的猜测是,在这种情况下,“Args...”被推导为“const int”(否则它将无法编译)。我的猜测正确吗?如果有人能提示我标准中的相关部分,那就太好了。
Let's say I have a variadic function template taking a function pointer to a function with said variadic arguments. The following code does not compile under gcc (11.2), but compiles under clang and msvc (https://godbolt.org/z/TWbEKWb9f).
#include <type_traits>
void dummyFunc(const int);
template<typename... Args>
void callFunc(void(*)(Args...), Args&&...); // <- this one is problematic
// see https://stackoverflow.com/questions/67081700/variadic-template-qualifiers
template<typename... Args>
void callFunc2(void(*)(std::conditional_t<std::is_const_v<Args>, const Args, Args>...), Args&&...); // <- this one works
int main()
{
// fails on gcc, works on clang and msvc
callFunc<const int>(&dummyFunc, 2);
// this works
//callFunc(&dummyFunc, 2);
// this works as well
//callFunc2<const int>(&dummyFunc, 2);
}
Specifying the function arguments 'Args...' explicitly as 'const int' prevents gcc from compiling the code. Apparently the template parameter expansion looses the cv-qualifier under gcc, while it is kept under clang and msvc. The error message is:
<source>: In function 'int main()':
<source>:15:24: error: no matching function for call to 'callFunc<const int>(void (*)(int), int)'
15 | callFunc<const int>(&dummyFunc, 2);
| ~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~
<source>:6:6: note: candidate: 'template<class ... Args> void callFunc(void (*)(Args ...), Args&& ...)'
6 | void callFunc(void(*)(Args...), Args&&...);
| ^~~~~~~~
<source>:6:6: note: template argument deduction/substitution failed:
<source>:15:24: note: types 'const int' and 'int' have incompatible cv-qualifiers
15 | callFunc<const int>(&dummyFunc, 2);
| ~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~
I found the 'solution' by utilizing type traits to make it compile here (Variadic template qualifiers), but I either should have to use this method or not, independently of the compiler. I know I don't have to specify the template arguments explicitly, but I want to understand why gcc fails to compile the code. Which compiler is "right", i.e. should this code compile or not (that is, should the cv-qualifier be dropped)?
As a side note, if I use a const reference instead, it compiles also under gcc, and the template argument is correctly recognized as 'const int&' (because the compiler absolutely has to do so). I am aware of the fact that, if the template argument is deduced by the compiler and not explicitly specified, that the cv-qualifier is dropped. But in my opinion gcc's behavior here is wrong because I explicitly stated the type to use. However, I don't know the standard well enough to tell whether gcc or the other two compilers are not following the standard. It seems to be related to variadic templates and not the template deduction itself, because the version without variadic templates works under gcc (https://godbolt.org/z/qn8a5bh5E):
void dummyFunc(const int);
template<typename Arg>
void callFunc(void(*)(Arg), Arg&&);
int main()
{
callFunc<const int>(dummyFunc, 2);
}
Also, why is automatic template type deduction even working in the first (problematic) case? 'Args...' should be deduced as both 'const int', because of the function pointer, as well as 'int' because of the second argument. My guess is that in this case, 'Args...' is deduced as 'const int' (otherwise it would not compile). Is my guess correct? It would be great if somebody could hint me to the relevant section in the standard.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
cv 限定符应该始终被删除(无论是在确定 dummyFunc 的类型时,还是在将推导的参数替换为 callFunc 签名时),而且我很确定所有编译器同意这一点。这并不是真正的问题所在。让我们稍微改变一下这个例子:
现在GCC 和 Clang 拒绝代码,而 MSVC 接受它。
GCC 和 Clang 都存在
const int
(显式指定)和int
(推导)之间不匹配的问题,而 MSVC 显然很乐意让Args
> = [const int
] 按照指定。谁是对的?据我所知,这里的问题是 [temp. arg.explicit]/9,其中指出:
因此,为包
Args
指定显式模板参数并不会阻止推导。编译器仍然必须尝试推导Args
,以防需要扩展它,以便将函数参数类型与参数类型进行匹配。从未明确解释过该标准的这一段内容的真正含义。我猜 MSVC 的方法可能类似于“推导包,就好像没有显式指定的模板参数一样,然后如果显式指定的模板参数不是推导的模板参数的前缀,则抛出结果”,这似乎是一种明智的方法来处理这段代码。 GCC 和 Clang 可能类似于“推导包,就好像没有显式指定的模板参数一样,然后如果显式指定的模板参数不是推导的模板参数的前缀,则失败”,这将导致代码格式错误,但这似乎是一个不幸的解释,因为它与在非可变参数情况下显式指定的模板参数的处理方式不一致。
上面的示例类似于 OP 示例的简化版本:
这里,尾随的
Args&&...
已被删除,这不会改变结果:与 OP 的代码一样,< a href="https://godbolt.org/z/rnYbv49hx" rel="nofollow noreferrer">Clang 和 MSVC 接受它,而 GCC 不接受。。只有 Clang 改变了它的观点:它接受这个,同时拒绝带有S
的那个。公平地说,这两个片段并不真正相似:带有S
的片段涉及隐式转换。但尚不清楚为什么 Clang 对它们有不同的对待。从我的角度来看,GCC 和 Clang 都在可变参数模板推导方面存在不同的错误,而 MSVC 在这两种情况下都做了正确的事情。但很难根据标准文本来证明情况确实如此。
The cv-qualifier should always be dropped (both in determining the type of
dummyFunc
and when substituting the deduced argument into thecallFunc
signature), and I'm pretty sure all compilers agree on this. It's not really what the question is about. Let's change the example a bit:Now GCC and Clang reject the code while MSVC accepts it.
GCC and Clang both have issues with the mismatch between
const int
(explicitly specified) andint
(deduced) whereas MSVC is evidently happy just to letArgs
= [const int
] as specified. Who is right?As I see it the problem here is [temp.arg.explicit]/9, which states:
Thus, specifying an explicit template argument for the pack
Args
does not prevent deduction. The compiler still has to try to deduceArgs
in case it needs to be extended in order to match the function parameter types against the argument types.It has never been clearly explained what this paragraph of the standard really means. I guess MSVC's approach could possibly be something like "deduce the pack as if there were no explicitly specified template arguments, then throw out the result if the explicitly specified template arguments are not a prefix of the deduced template arguments" which seems like a sensible way to handle this code. GCC and Clang might be something like "deduce the pack as if there were no explicitly specified template arguments, and then fail if the explicitly specified template arguments are not a prefix of the deduced template arguments" which would lead to the code being ill-formed, but this seems like an unfortunate interpretation because it's inconsistent with how explicitly specified template arguments are treated in non-variadic cases.
The example above is similar to a simplified version of the OP's example:
Here, the trailing
Args&&...
has been removed, which doesn't change the result: as with OP's code, Clang and MSVC accept it while GCC doesn't.. Only Clang has changed its opinion: it accepts this one while rejecting the one withS
. To be fair, these two snippets are not really analogous: the one withS
involves an implicit conversion. But it's not clear why Clang treats them differently.From my point of view, GCC and Clang both have different bugs with variadic template deduction, while MSVC does the right thing in both cases. But it's hard to make an argument based on the standard text that this is unambiguously the case.