variadic模板参数包扩展释放资格

发布于 2025-01-17 20:44:35 字数 2728 浏览 3 评论 0原文

假设我有一个可变参数函数模板,它将函数指针指向具有所述可变参数的函数。以下代码不能在 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 技术交流群。

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

发布评论

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

评论(1

弱骨蛰伏 2025-01-24 20:44:35

cv 限定符应该始终被删除(无论是在确定 dummyFunc 的类型时,还是在将推导的参数替换为 callFunc 签名时),而且我很确定所有编译器同意这一点。这并不是真正的问题所在。让我们稍微改变一下这个例子:

template <class... Args> struct S {
    template <class... T> S(T...) {}
};

template<typename... Args>
void callFunc2(S<Args...>);

int main()
{
    callFunc2<const int>(S<int>{});
}

现在GCC 和 Clang 拒绝代码,而 MSVC 接受它

GCC 和 Clang 都存在 const int (显式指定)和 int (推导)之间不匹配的问题,而 MSVC 显然很乐意让 Args > = [const int] 按照指定。谁是对的?

据我所知,这里的问题是 [temp. arg.explicit]/9,其中指出:

模板实参推导可以扩展与模板参数包对应的模板实参序列,即使该序列包含显式指定的模板实参。

因此,为包 Args 指定显式模板参数并不会阻止推导。编译器仍然必须尝试推导 Args,以防需要扩展它,以便将函数参数类型与参数类型进行匹配。

从未明确解释过该标准的这一段内容的真正含义。我猜 MSVC 的方法可能类似于“推导包,就好像没有显式指定的模板参数一样,然后如果显式指定的模板参数不是推导的模板参数的前缀,则抛出结果”,这似乎是一种明智的方法来处理这段代码。 GCC 和 Clang 可能类似于“推导包,就好像没有显式指定的模板参数一样,然后如果显式指定的模板参数不是推导的模板参数的前缀,则失败”,这将导致代码格式错误,但这似乎是一个不幸的解释,因为它与在非可变参数情况下显式指定的模板参数的处理方式不一致。

上面的示例类似于 OP 示例的简化版本:

void dummyFunc(int);

template<typename... Args>
void callFunc(void(*)(Args...));

int main()
{
    callFunc<const int>(&dummyFunc);
}

这里,尾随的 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 the callFunc 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:

template <class... Args> struct S {
    template <class... T> S(T...) {}
};

template<typename... Args>
void callFunc2(S<Args...>);

int main()
{
    callFunc2<const int>(S<int>{});
}

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) and int (deduced) whereas MSVC is evidently happy just to let Args = [const int] as specified. Who is right?

As I see it the problem here is [temp.arg.explicit]/9, which states:

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.

Thus, specifying an explicit template argument for the pack Args does not prevent deduction. The compiler still has to try to deduce Args 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:

void dummyFunc(int);

template<typename... Args>
void callFunc(void(*)(Args...));

int main()
{
    callFunc<const int>(&dummyFunc);
}

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 with S. To be fair, these two snippets are not really analogous: the one with S 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.

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