哪种类型特征表明该类型是 memcpy 可分配的? (元组,对)

Which type trait would indicate that type is memcpy assignable? (tuple, pair)

我想知道我可以做什么类型内省来检测可通过简单的原始内存复制分配的类型?

例如,据我了解,内置类型的元组和此类元组的元组属于此类。 动机是我想尽可能地传输原始字节。

T t1(...); // not necessarely default constructible 
T t2(...);

t1 = t2; // should be equivalent to std::memcpy(&t1, &t2, sizeof(T));
// t1 is now an (independent) copy of the value of t2, for example each can go out of scope independently

type_trait 组合 type_traits 可以在编译时告诉我们赋值是否可以(原则上)替换为 memcpy?

我尝试了哪些类型适用于我认为应该满足此条件的类型,令我惊讶的是,唯一符合该行为的不是 std::is_trivially_assignable,而是 std::trivially_destructible。 这在某种程度上是有道理的,但我很困惑为什么其他一些选项不适用于预期的情况。

我知道可能没有防弹方法,因为人们总是可以写一个 class 有效地可 memcopyable,不能 "detected" 作为 memcopyable,但我正在寻找一个适用于简单直观的案例。

#include<type_traits>
template<class T> using trait = 
    std::is_trivially_destructible
//  std::is_trivial
//  std::is_trivially_copy_assignable
//  std::is_trivially_copyable // // std::tuple<double, double> is not trivially copyable!!!
//  std::is_trivially_default_constructible
//  std::is_trivially_default_constructible
//  std::is_trivially_constructible
//  std::is_pod // std::tuple<double, double> is not pod!!!
//  std::is_standard_layout
//  std::is_aggregate
//  std::has_unique_object_representations
    <T>
;

int main(){
    static_assert((trait<double>{}), "");
    static_assert((trait<std::tuple<double, double>>{}), "");
    static_assert((not trait<std::tuple<double, std::vector<double>>>{}), "");
    static_assert((not trait<std::vector<double>>{}), "");
}

当然,我认为元组应该是 memcopyable 的信念不是基于标准,而是基于常识和实践。也就是说,因为这通常是可以的:

std::tuple<double, std::tuple<char, int> > t1 = {5.1, {'c', 8}};
std::tuple<double, std::tuple<char, int> > t2;
t2 = t1;
std::tuple<double, std::tuple<char, int> > t3;
std::memcpy(&t3, &t1, sizeof(t1));
assert(t3 == t2);

作为原理证明,我实现了这个。 我添加了几个与大小相关的条件,以避免 std::tuple.

的一些可能的误导性专业化
template<class T> 
struct is_memcopyable 
: std::integral_constant<bool, std::is_trivially_copyable<T>{}>{};

template<class T, class... Ts> 
struct is_memcopyable<std::tuple<T, Ts...>> : 
    std::integral_constant<bool, 
        is_memcopyable<T>{} and is_memcopyable<std::tuple<Ts...>>{}
    >
{};

template<class T1, class T2> 
struct is_memcopyable<std::pair<T1, T2>> : 
    std::integral_constant<bool, 
        is_memcopyable<T1>{} and is_memcopyable<T2>{}
    >
{};

这是一个非常有限的解决方法,因为 class 如:

struct A{ std::tuple<double, double> t; }; 

不幸的是,仍将被报告为不可平凡复制和不可内存复制。

这只是对您问题的部分回答:

类型特征不一定代表其名称的字面意思。

具体来说,我们取std::is_trivially_copyable。你是 - 正确地 - 惊讶于两个双元组不是平凡可复制的。怎么可能?!

嗯,trait definition 说:

If T is a TriviallyCopyable type, provides the member constant value equal true. For any other type, value is false.

TriviallyCopyable concept 在其定义中有以下要求:

  • Every copy constructor is trivial or deleted
  • Every move constructor is trivial or deleted
  • Every copy assignment operator is trivial or deleted
  • Every move assignment operator is trivial or deleted
  • At least one copy constructor, move constructor, copy assignment operator, or move assignment operator is non-deleted
  • Trivial non-deleted destructor

不是您所期望的那样,对吗?

总而言之,任何标准库特性都不一定能满足 "constructible by memcpy()'ing" 的确切要求。

尝试回答您的问题:std::memcpy() 没有任何直接要求,但有这些规定:

  • Copies count bytes from the object pointed to by src to the object pointed to by dest. Both objects are reinterpreted as arrays of unsigned char.
  • 如果对象重叠,则行为未定义。
  • 如果 dest 或 src 是空指针,则行为未定义,即使计数为零。
  • 如果对象不是 TriviallyCopyable,则 memcpy 的行为未指定且可能未定义。

现在要使一个对象具有平凡可复制的资格,必须满足以下条件或要求:

  • Every copy constructor is trivial or deleted
  • 每个移动构造函数都是微不足道的或已删除
  • 每个复制赋值运算符都是微不足道的或被删除的
  • 每个移动赋值运算符都是微不足道的或已删除
  • 复制构造函数、移动构造函数、复制赋值运算符或移动赋值运算符中至少有一个是non-deleted
  • 平凡的non-deleted析构函数

This implies that the class has no virtual functions or virtual base classes.

Scalar types and arrays of TriviallyCopyable objects are TriviallyCopyable as well, as well as the const-qualified (but not volatile-qualified) versions of such types.


这导致我们 std::is_trivially_copyable

If T is a TriviallyCopyable type, provides the member constant value equal true. For any other type, value is false.

唯一可平凡复制的类型是标量类型,可平凡复制 类,以及此类 types/classes 的数组(可能 const-qualified,但不是 volatile-qualified)。

如果 std::remove_all_extents_t 是不完整的类型并且不是(可能 cv-qualified)void,则行为未定义。

从 c++17 开始就有这个不错的特性:

Helper variable template

template< class T >
inline constexpr bool is_trivially_copyable_v = is_trivially_copyable<T>::value; 

并且您想尝试使用 type_traitstd::tuple<>std::memcpy() 一起使用。

但我们需要问问自己 std::tuple 是否是 Trivially Copyable 以及为什么?

我们可以在这里看到答案: 并根据那个答案;这不是因为标准不要求 copy/move 赋值运算符是微不足道的。


所以我认为有效的答案是这样的:否 std::tuple 不是平凡可复制的,但 std::memcpy() 不需要它,但只说明如果它不是'吨;它是 UB。那么你可以使用 std::tuplestd::memcpy 吗?我想是的,但它安全吗?这可能会有所不同并会产生 UB。

那么我们可以从这里做什么呢?冒险?可能是。我发现了其他相关的东西,但没有发现任何关于它是否可平凡复制的信息。它不是 type_trait,但可以与 std::tuplestd::memcpy 结合使用,即 std::tuple_element。您也许可以使用它来执行 memcpy,但我对此并不完全确定。我已经搜索以了解有关 std::tuple_element 的更多信息,以查看它是否可平凡复制,但没有找到太多,所以我所能做的就是测试一下 Visual Studio 2017 说的内容:

template<class... Args>
struct type_list {
    template<std::size_t N>
    using type = typename std::tuple_element<N, std::tuple<Args...>>::type;
};

int main() {
    std::cout << std::boolalpha;
    std::cout << std::is_trivially_copyable<type_list<int, float, float>>::value << '\n';
    std::cout << std::is_trivially_copyable<std::tuple<int, float, float>>::value << '\n';

    _getch(); // used to stop visual studio debugger from closing.
    return 0;
}

输出:

true
false

因此,如果我们将 std::tuple_element 包装在一个结构中,它似乎是可平凡复制的。现在的问题是如何将其与 std::tuple 数据集集成,以便将它们与 std::memcpy() 一起使用成为 type safe。不确定我们是否可以,因为 std::tuple_element 将 return tuple.

中的元素类型

如果我们甚至尝试将 tuple 包装在这样的结构中:

template<class... Args>
struct wrapper {
    std::tuple<Args...> t;
};

我们可以通过以下方式检查:

{
    std::cout << std::is_trivially_copyable< wrapper<int, float, float> >::value << std::endl;
}

还是false。但是我们已经看到 std::tuple 已经在第一个结构中使用,而结构 returned true。这可能对你有一些帮助,确保你可以安全地使用std::memcpy,但我不能保证。只是编译器似乎同意它。所以这可能是最接近可能有效的 type_trait 的东西。


注意: - 所有关于 memcpyTrivially Copyable conceptsis_trivially_copyablestd::tuple 和 [=30= 的参考] 摘自 cppreference 及其相关页面。

正确的测试实际上是 std::is_trivially_copyable,它允许使用 memcpy 创建新对象和修改现有对象。

虽然您可能会惊讶这些 return 错误的类型,您的直觉告诉您 memcpy 应该没问题,但它们并没有说谎;在这些情况下,标准确实 memcpy 未定义的行为。


std::pair 的特殊情况下,我们可以深入了解问题所在:

int main()
{
    typedef std::pair<double,double> P;
    std::cout << "\nTC:  " << std::is_trivially_copyable<P>::value;
    std::cout << "\nTCC: " << std::is_trivially_copy_constructible<P>::value;
    std::cout << "\nTCv: " << std::is_trivially_constructible<P, const P&>::value;
    std::cout << "\n CC: " << std::is_copy_constructible<P>::value;
    std::cout << "\n MC: " << std::is_move_constructible<P>::value;
    std::cout << "\nTCA: " << std::is_trivially_copy_assignable<P>::value;
    std::cout << "\nTCvA:" << std::is_trivially_assignable<P, const P&>::value;
    std::cout << "\n CA: " << std::is_copy_assignable<P>::value;
    std::cout << "\n MA: " << std::is_move_assignable<P>::value;
    std::cout << "\nTD:  " << std::is_trivially_destructible<P>::value;
}

TC: 0 TCC: 1 TCv: 1 CC: 1 MC: 1 TCA: 0 TCvA:0 CA: 1 MA: 1 TD: 1

显然它不是简单的复制分配。1

pair 赋值运算符是 user-defined,所以很重要。


1我觉得这里clang,gcc,msvc其实都是错的,但是如果满足std::_is_trivially_copy_assignable也无济于事,因为TriviallyCopyable需要复制构造函数(如果未删除)是微不足道的,而不是 TriviallyCopyAssignable 特征。是的,他们是不同的。

A copy/move assignment operator for class X is trivial if it is not user-provided and...

is_assignable_v<T, const T&> is true and the assignment, as defined by is_assignable, is known to call no operation that is not trivial.

pair<double, double>的复制赋值运算符调用的操作是对单个双打的赋值,微不足道。

不幸的是,trivially copyable 的定义依赖于第一个,pair 失败了。

A trivially copyable class is a class:

  • where each copy constructor, move constructor, copy assignment operator, and move assignment operator is either deleted or trivial,
  • that has at least one non-deleted copy constructor, move constructor, copy assignment operator, or move assignment operator, and
  • that has a trivial, non-deleted destructor.