为什么模板参数解包有时对 std::function 不起作用?
Why does template parameter unpacking sometimes not work for std::function?
我遇到了问题。当我使用 std::function<A(Fs...)>
之类的东西时,它不起作用,但 std::function<A(Fs..., B)>
确实起作用。这是在 Clang 8.0 下; none 它在 GCC 下工作。
这是示例:
#include <functional>
template<typename A, typename B, typename ...Fs>
void func_tmpl1(std::function<A(Fs..., B)> callable)
{
}
template<typename A, typename ...Fs>
void func_tmpl2(std::function<A(Fs...)> callable)
{
}
class Cls1{};
void func0(std::function<void(float, Cls1)> callable)
{
}
int main()
{
std::function<void(float, Cls1)> f1 = [](float a, Cls1 b){};
func0(f1);
func0([](float a, Cls1 b){});
func_tmpl1<void, Cls1, float>(f1); // fails in GCC
func_tmpl2<void, float, Cls1>(f1);
func_tmpl1<void, Cls1, float>( // fails in GCC
[](float a, Cls1 b)
{
}
);
func_tmpl2<void, float, Cls1>( // fails in both
[](float a, Cls1 b)
{}
);
return 0;
}
在Godbolt,我们可以看到GCC总是失败,但Clang只在最后一次函数调用时失败。谁能解释一下这里发生了什么?
为方便起见,我们调用代码中的三个失败调用 #1、#2 和 #3。
问题是,当模板参数包对应的模板参数被显式指定时,模板参数包是否仍然参与模板参数推导,如果参与,推导失败是否导致整个调用格式错误?
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.
我们可以推断模板参数推导仍然应该执行。
在func_tmpl1
的声明中,std::function<A(Fs..., B)>
是一个非推导上下文([temp.deduct.type]/9: "If the template argument list of P contains a pack expansion that is not the last template argument, the entire template argument list is a non-deduced context."), so template argument deduction for Fs
should be ignored and #1 and #2 are both well-formed. There is a GCC bug report.
对于#3,模板参数推导显然失败了(std::function<A(Fs...)>
与 lambda 类型相比),但推导失败真的会使代码格式错误吗?在我看来,标准对此并不清楚,还有一个related issue。从 CWG 的回复来看,#3 确实格式错误。
这看起来像是一个编译器错误;当所有参数都已明确指定且因此不需要推导时,编译器会尝试模板参数推导。
或者错误可能与替换有关,应该会成功。
根据标准,可以显式指定可变包参数。请参阅 [temp.arg.explicit]/5 中的示例:
template<class ... Args> void f2();
void g() {
f2<char, short, int, long>(); // OK
}
当所有模板参数都已知时,编译器应该简单地实例化模板并完成它;重载决议然后正常进行。
要解决此问题,我们可以通过引入非推导上下文来禁用模板参数推导。例如像这样:
template<typename T> using no_deduce = typename std::common_type<T>::type;
template<typename A, typename B, typename ...Fs>
void func_tmpl1(no_deduce<std::function<A(Fs..., B)>> callable)
{
}
template<typename A, typename ...Fs>
void func_tmpl2(no_deduce<std::function<A(Fs...)>> callable)
{
}
(这里的::type
是依赖类型,成为非推导上下文)
现在它可以在 g++
和 clang++
中正常编译。 link to coliru
话虽如此,请注意 std::function
主要用于 类型擦除 并且是 昂贵的抽象 因为它会产生在 运行 时间的额外间接寻址是一个沉重的传递对象,因为它试图存储任何可能的仿函数的副本,同时避免堆分配(这仍然经常发生 - 然后它是一个大的 empty 对象加上堆分配。
因为你的函数已经是模板,你真的不需要类型擦除;将 callable
作为模板参数更容易和更有效。
template<typename Func>
void func_tmpl(Func callable) // that's all
{
}
或者,如果您必须通过 callable
个参数来区分,可以使用一些 SFINAE:
#include <functional>
class Cls1{};
template<typename A, typename B, typename ...Fs, typename Func,
typename = std::enable_if_t<std::is_invocable_r_v<A, Func, Fs..., B> > >
void func_tmpl1(Func callable)
{
}
template<typename A, typename B, typename ...Fs, typename Func,
typename = std::enable_if_t<std::is_invocable_r_v<A, Func, B, Fs...> > >
void func_tmpl2(Func callable)
{
}
void func0(std::function<void(float, Cls1)> callable)
{
}
int main()
{
std::function<void(float, Cls1)> f1 = [](float a, Cls1 b){};
func0(f1); // func0 is not a template - so it requires type erasure
func0([](float a, Cls1 b){});
func_tmpl1<void, Cls1, float>(f1); // #1 OK
func_tmpl2<void, float, Cls1>(f1); // #2 OK
func_tmpl1<void, Cls1, float>([](float a, Cls1 b) {}); // #3 OK
func_tmpl2<void, float, Cls1>([](float a, Cls1 b) {}); // #4 OK
return 0;
}
我遇到了问题。当我使用 std::function<A(Fs...)>
之类的东西时,它不起作用,但 std::function<A(Fs..., B)>
确实起作用。这是在 Clang 8.0 下; none 它在 GCC 下工作。
这是示例:
#include <functional>
template<typename A, typename B, typename ...Fs>
void func_tmpl1(std::function<A(Fs..., B)> callable)
{
}
template<typename A, typename ...Fs>
void func_tmpl2(std::function<A(Fs...)> callable)
{
}
class Cls1{};
void func0(std::function<void(float, Cls1)> callable)
{
}
int main()
{
std::function<void(float, Cls1)> f1 = [](float a, Cls1 b){};
func0(f1);
func0([](float a, Cls1 b){});
func_tmpl1<void, Cls1, float>(f1); // fails in GCC
func_tmpl2<void, float, Cls1>(f1);
func_tmpl1<void, Cls1, float>( // fails in GCC
[](float a, Cls1 b)
{
}
);
func_tmpl2<void, float, Cls1>( // fails in both
[](float a, Cls1 b)
{}
);
return 0;
}
在Godbolt,我们可以看到GCC总是失败,但Clang只在最后一次函数调用时失败。谁能解释一下这里发生了什么?
为方便起见,我们调用代码中的三个失败调用 #1、#2 和 #3。
问题是,当模板参数包对应的模板参数被显式指定时,模板参数包是否仍然参与模板参数推导,如果参与,推导失败是否导致整个调用格式错误?
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.
我们可以推断模板参数推导仍然应该执行。
在func_tmpl1
的声明中,std::function<A(Fs..., B)>
是一个非推导上下文([temp.deduct.type]/9: "If the template argument list of P contains a pack expansion that is not the last template argument, the entire template argument list is a non-deduced context."), so template argument deduction for Fs
should be ignored and #1 and #2 are both well-formed. There is a GCC bug report.
对于#3,模板参数推导显然失败了(std::function<A(Fs...)>
与 lambda 类型相比),但推导失败真的会使代码格式错误吗?在我看来,标准对此并不清楚,还有一个related issue。从 CWG 的回复来看,#3 确实格式错误。
这看起来像是一个编译器错误;当所有参数都已明确指定且因此不需要推导时,编译器会尝试模板参数推导。 或者错误可能与替换有关,应该会成功。
根据标准,可以显式指定可变包参数。请参阅 [temp.arg.explicit]/5 中的示例:
template<class ... Args> void f2(); void g() { f2<char, short, int, long>(); // OK }
当所有模板参数都已知时,编译器应该简单地实例化模板并完成它;重载决议然后正常进行。
要解决此问题,我们可以通过引入非推导上下文来禁用模板参数推导。例如像这样:
template<typename T> using no_deduce = typename std::common_type<T>::type;
template<typename A, typename B, typename ...Fs>
void func_tmpl1(no_deduce<std::function<A(Fs..., B)>> callable)
{
}
template<typename A, typename ...Fs>
void func_tmpl2(no_deduce<std::function<A(Fs...)>> callable)
{
}
(这里的::type
是依赖类型,成为非推导上下文)
现在它可以在 g++
和 clang++
中正常编译。 link to coliru
话虽如此,请注意 std::function
主要用于 类型擦除 并且是 昂贵的抽象 因为它会产生在 运行 时间的额外间接寻址是一个沉重的传递对象,因为它试图存储任何可能的仿函数的副本,同时避免堆分配(这仍然经常发生 - 然后它是一个大的 empty 对象加上堆分配。
因为你的函数已经是模板,你真的不需要类型擦除;将 callable
作为模板参数更容易和更有效。
template<typename Func>
void func_tmpl(Func callable) // that's all
{
}
或者,如果您必须通过 callable
个参数来区分,可以使用一些 SFINAE:
#include <functional>
class Cls1{};
template<typename A, typename B, typename ...Fs, typename Func,
typename = std::enable_if_t<std::is_invocable_r_v<A, Func, Fs..., B> > >
void func_tmpl1(Func callable)
{
}
template<typename A, typename B, typename ...Fs, typename Func,
typename = std::enable_if_t<std::is_invocable_r_v<A, Func, B, Fs...> > >
void func_tmpl2(Func callable)
{
}
void func0(std::function<void(float, Cls1)> callable)
{
}
int main()
{
std::function<void(float, Cls1)> f1 = [](float a, Cls1 b){};
func0(f1); // func0 is not a template - so it requires type erasure
func0([](float a, Cls1 b){});
func_tmpl1<void, Cls1, float>(f1); // #1 OK
func_tmpl2<void, float, Cls1>(f1); // #2 OK
func_tmpl1<void, Cls1, float>([](float a, Cls1 b) {}); // #3 OK
func_tmpl2<void, float, Cls1>([](float a, Cls1 b) {}); // #4 OK
return 0;
}