c++ - C++ 标准是否对向量赋值函数或构造函数有明确要求?
问题描述
在可能有歧义的地方,我将参考 C++14 标准。
以https://en.cppreference.com/w/cpp/container/vector作为参考,T
对于vector的template参数要求比较稀疏
对元素施加的要求取决于对容器执行的实际操作。一般要求元素类型是完整类型,满足Erasable的要求,但很多成员函数的要求更严格。
事实上,许多页面(例如用于插入的页面)都非常明确地说明了所放置的要求T
。可悲的是,构造函数或分配函数并非如此。这是网站遗漏还是标准遗漏?
动机
这个问题的动机有点复杂。我有一个Node
代表树中节点的类。每个节点都有一个父节点(由 a 表示Node*
)和一个子节点列表(由 a 表示std::vector<Node, MyAllocator>
)。最后一点是有趣的地方。为了确保Node
s 继续指向正确的父母,我实现了一个自定义分配器,它使用指向正确的指针“预加载”每个构造函数调用Node
。代码如下
#ifndef PRElOAD_ALLOCATOR_H
#define PRElOAD_ALLOCATOR_H
#include <tuple>
#include <utility>
#include <memory>
#include <vector>
/**
* @class PreloadAllocator
* Provides a generic allocator that is able to 'preload' some arguments for the
* construct function. Note that these arguments are *always* loaded at the
* front of any constructor call so modified move, copy constructors need to
* created to match.
* This allocator will only work with simple containers like vector that do not
* modify the type of the allocator being used. *It will not work* with types
* like list and map that internally represent the data in a different format.
* This is because the constructors for these objects behave differently and it
* is not simple to know how to insert the extra arguments into such a call.
*/
template <typename T, typename... Args>
class PreloadAllocator {
public:
// Standard allocator typedefs
using value_type = T;
using pointer = T*;
using const_pointer = const T*;
using reference = T&;
using const_reference = const T&;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
PreloadAllocator(Args&&... args)
: tup(std::forward_as_tuple(args...) ) {}
template <typename U>
PreloadAllocator(const PreloadAllocator<U, Args...>& other)
: tup(other.tup) {}
pointer allocate(size_type count, const_pointer hint = 0) {
return m_defaultAlloc.allocate(count, hint);
}
void deallocate(pointer ptr, size_type count) {
return m_defaultAlloc.deallocate(ptr, count);
}
template <typename... Ts>
void construct(T* ptr, Ts&&... ts) {
// First preload the constructor arguments with the allocator's
// versions, then add on the rest, and let the default allocator do the
// rest.
return construct_impl<Ts...>(
ptr, std::forward<Ts>(ts)..., std::index_sequence_for<Args...>{});
}
// Hold the arguments that we will preload into every constructor call
const std::tuple<Args...> tup;
private:
// Compose the default STL allocator to use its version of allocate and
// deallocate.
std::allocator<T> m_defaultAlloc;
// Actual function that does the constructing
template <typename... Ts, std::size_t... Is>
void construct_impl(T* ptr, Ts&&... ts, std::index_sequence<Is...>) {
return m_defaultAlloc.construct(
ptr, std::get<Is>(tup)..., std::forward<Ts>(ts)...);
}
};
template <typename T, typename... Args>
bool operator==(
const PreloadAllocator<T, Args...>& lhs,
const PreloadAllocator<T, Args...>& rhs)
{
return lhs.tup == rhs.tup;
}
template <typename T, typename... Args>
bool operator!=(
const PreloadAllocator<T, Args...>& lhs,
const PreloadAllocator<T, Args...>& rhs)
{
return !(lhs==rhs);
}
#endif //> !PRElOAD_ALLOCATOR_H
class Node {
public:
using alloc_t = PreloadAllocator<Node, Node*>;
using vec_t = std::vector<Node, alloc_t>;
Node(Node* parent, int data)
: parent(parent), data(data), children(alloc_t(this) ) {}
Node(Node* parent, const Node& other)
: parent(parent), data(other.data), children(other.children, alloc_t(this) ) {}
Node(Node* parent, Node&& other)
: parent(parent), data(std::move(other.data) ), children(std::move(other.children), alloc_t(this) ) {}
// Copy/Move constructing is not going to give us the correct parent!
Node(const Node&) = delete;
Node(Node&&) = delete;
Node* parent;
int data;
vec_t children;
};
int main() {
Node root(nullptr, 0);
}
在 g++ 中编译此代码时,它会编译并运行良好。但是,当我在 clang 中编译时,出现以下错误
In file included from alloc_test.cxx:3:
/Library/Developer/CommandLineTools/usr/include/c++/v1/vector:1275:9: error: no matching member function for call to 'assign'
assign(_Ip(__x.begin()), _Ip(__x.end()));
^~~~~~
alloc_test.cxx:15:55: note: in instantiation of member function 'std::__1::vector<Node, PreloadAllocator<Node, Node *> >::vector' requested here
: parent(parent), data(std::move(other.data) ), children(std::move(other.children), alloc_t(this) ) {}
^
/Library/Developer/CommandLineTools/usr/include/c++/v1/vector:588:10: note: candidate function not viable: no known conversion from '_Ip' (aka 'move_iterator<__wrap_iter<Node *> >') to
'std::__1::vector<Node, PreloadAllocator<Node, Node *> >::size_type' (aka 'unsigned long') for 1st argument
void assign(size_type __n, const_reference __u);
^
/Library/Developer/CommandLineTools/usr/include/c++/v1/vector:576:9: note: candidate template ignored: requirement '!__is_forward_iterator<move_iterator<__wrap_iter<Node *> > >::value' was not satisfied
[with _InputIterator = std::__1::move_iterator<std::__1::__wrap_iter<Node *> >]
assign(_InputIterator __first, _InputIterator __last);
^
/Library/Developer/CommandLineTools/usr/include/c++/v1/vector:586:9: note: candidate template ignored: requirement 'is_constructible<value_type, typename iterator_traits<move_iterator<__wrap_iter<Node *>
> >::reference>::value' was not satisfied [with _ForwardIterator = std::__1::move_iterator<std::__1::__wrap_iter<Node *> >]
assign(_ForwardIterator __first, _ForwardIterator __last);
^
/Library/Developer/CommandLineTools/usr/include/c++/v1/vector:592:10: note: candidate function not viable: requires single argument '__il', but 2 arguments were provided
void assign(initializer_list<value_type> __il)
^
1 error generated.
那么,这是 g++ 和 clang 决定以不同方式解释的标准中的歧义,还是其中一个真正的错误?
尝试编写这种预加载分配器是对标准的不可原谅的滥用吗?
*编辑:*修复了代码片段中缺失的数据成员。
解决方案
您的代码似乎对 C++17 标准有效。最强的要求来自这个拷贝构造函数std::vector
:
children(other.children, alloc_t(this))
该标准说(表 65)这Node
需要Cpp17CopyInsertable进入std::vector<Node, PreloadAllocator<Node, Node*>>
:
T
is Cpp17CopyInsertable intoX
意味着,除了T
是Cpp17MoveInsertable into之外X
,以下表达式是格式正确的:allocator_traits<A>::construct(m, p, v)
并且它的求值导致以下后置条件成立: 的值
v
不变并且等价于*p
。
Cpp17MoveInsertable的要求类似,只是v
是一个右值,后置条件不同。
查看错误时clang
,编译器似乎正在检查MoveConstructible的要求:
is_constructible<
value_type,
typename iterator_traits<move_iterator<__wrap_iter<Node *> >::reference>::value
...这与Cpp17MoveInsertable的不同。
从设计的角度来看,我会亲自删除该自定义分配器,而是parent
在需要时手动更新成员,例如:
Node(Node* parent, const Node& other)
: parent(parent), data(other.data), children(other.children) {
fix_parent_in_childrens();
}
void fix_parent_in_childrens() {
for (Node &node: children) {
node.parent = this;
}
}
由于您似乎已经有了正确的封装,因此(对我而言)这比自定义分配器更有意义。
推荐阅读
- python - 如何在 Flask 网页中嵌入模板化的散景应用程序?
- javascript - 合并子变量以获取构造变量
- local-storage - 在 office word add in 中使用 openBrowserWindow 时,localstorage 不起作用
- j - 使用 J 控制台时获得整体结果
- wordpress - 想找到一种方法来自定义 wordpress 经典块,如编辑古腾布格
- ms-access - 访问运行时 - 信任应用程序?
- azure - LINUX 链接服务器上的 Azure Data Studio 未展开以显示数据库
- blockchain - 智能合约不断听取价格信息并在达到价格后立即执行的最佳方式是什么?
- variables - 汇编语言定义整数变量
- python - Spyder 中的变量资源管理器不显示变量值