模板模板推导给了我一个通用引用的错误

template template deduction give me an error with universal references

本文关键字:一个 引用 错误      更新时间:2023-10-16

为了真正理解C++17折表达式,我为容器编写了一个函数附加:

#include <iostream>
#include <vector>
#include <list>
// fold expression
template<typename T, typename U, template<typename> typename... Args>
inline void append_impl(std::vector<T>& v, Args<U>... args) noexcept
{
static_assert((std::is_constructible_v<T,U>));
std::cout << "append_impl version one " << std::endl;
(v.insert(std::end(v),
std::begin(args),
std::end  (args)), ...);
}
//fold expression
template<typename T, typename... Args>
inline void append_impl(std::vector<T>& v, Args&&... args) noexcept
{
static_assert((std::is_constructible_v<T, Args&&> && ...));
std::cout << "append_impl version two " << std::endl;
(v.push_back(std::forward<Args>(args)), ...);
}
// fold expression
template<typename T, typename... Args>
inline void append(std::vector<T>& v, Args&&... args) noexcept
{
(append_impl(v, args), ...);
}

int main()
{
std::vector<int> a = {1,2};
std::vector<int> b = {3,4};
std::vector<int> c = {5,6};
std::list<int> d = {15,16};
append(a,b,c, std::vector<int>{8,9}, 10, 11, 12, 13, 14, d);

for(const auto& e : a)
{
std::cout << e << " ";
}
std::cout << std::endl;
return 0;
}

这工作正常并给我结果:

append_impl版本一

append_impl版本一

append_impl版本一

append_impl版本二

append_impl版本二

append_impl版本二

append_impl版本二

append_impl版本二

append_impl版本一

1 2 3 4 5 6 8 9 10 11 12 13 14 15 16

但是我从这段代码中有一个问题:

  • 在 append_impl 的第一个版本中,args 是按副本传递的。我想使用通用引用(在斯科特·迈耶斯的意义上(来避免这种情况,但append_impl(std::vector<T>& v, Args<U>&&... args)给我一个编译错误。
rog.cc: In instantiation of 'void append_impl(std::vector<T>&, Args&& ...) [with T = int; Args = {std::vector<int, std::allocator<int> >&}]':
prog.cc:18:15:   required from 'void append(std::vector<T>&, Args&& ...) [with T = int; Args = {std::vector<int, std::allocator<int> >&, std::vector<int, std::allocator<int> >&, std::vector<int, std::allocator<int> >, int, int, int, int, int, std::__cxx11::list<int, std::allocator<int> >&}]'
prog.cc:29:59:   required from here
prog.cc:10:3: error: static assertion failed
10 |   static_assert((std::is_constructible_v<T, Args&&> && ...));
|   ^~~~~~~~~~~~~
prog.cc:12:15: error: no matching function for call to 'std::vector<int>::push_back(std::vector<int>&)'
12 |   (v.push_back(std::forward<Args>(args)), ...);

为什么以及我可以做些什么来避免复制?

代码存在以下问题:


你忘了#include<type_traits>.


您忘记转发append

(append_impl(v, std::forward<Args>(args)), ...);

append_impl中的模板模板参数有点问题。因为它是按template<typename> typename... Args编写的,所以它假定传递给它的模板采用一个模板参数。std::list(和其他容器(采用多个模板参数(尽管其他参数是默认的(。尚不完全清楚此类模板是否对您的模板模板参数有效。

GCC接受它,而Clang不接受(见 https://godbolt.org/z/LY9r-k(。我认为在解决 CWG 问题 150 GCC 接受代码是正确的,但我没有详细检查。

无论如何,通过将参数更改为template<typename...> typename... Args可以轻松避免此问题,它接受具有任意多个参数的模板。


您的实际问题:通用引用(或转发引用(仅在模板参数T直接用作函数参数中的T&&时才有效。如果在函数参数的模板参数中使用它,则导致通用引用的转发行为的引用折叠规则不适用。

Args<U>&&... args始终是一个正确的参考,无论U有什么参考资格。

因此,如果您尝试将左值引用转发到此函数模板,则代码不会编译,该模板始终需要右值引用。

您可能应该为右值引用编写一个模板重载,为const值引用编写一个模板重载:

template<typename T, typename U, template<typename...> typename... Args>
inline void append_impl(std::vector<T>& v, const Args<U>&... args) noexcept
template<typename T, typename U, template<typename...> typename... Args>
inline void append_impl(std::vector<T>& v, Args<U>&&... args) noexcept

但是,您会遇到一个问题,即非const左值引用将更好地匹配直接Args&&的第二个模板重载,因此您需要添加另一个重载:

template<typename T, typename U, template<typename...> typename... Args>
inline void append_impl(std::vector<T>& v, Args<U>&... args) noexcept 
{
append_impl(v, std::as_const(args)...);
}

(需要#include<utility>(


通常,您尝试识别容器的方式不会保存。对于由模板类形成且不是容器的所有类型,它将失败。

相反,您可以在要使用的容器接口上通过 SFINAE 选择正确的函数重载,即std::begin(arg)std::end(arg)insert电话。您可以使用表达式 SFINAE 来执行此操作。

或者,您可以为这些表达式的格式良好编写类型特征,并对根据检查类型特征if constexpr选择实现的append_impl使用单个重载。

在C++20中,这将以更简单的方式使用概念来实现。

似乎也没有任何理由append_implArgs作为参数包。它只用一个参数来调用Args