哪种类型特征表明该类型是memcpy可分配的? (元组,一对) [英] Which type trait would indicate that type is memcpy assignable? (tuple, pair)

查看:70
本文介绍了哪种类型特征表明该类型是memcpy可分配的? (元组,一对)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想知道我可以做什么类型的自检来检测仅由原始内存副本可分配的类型?

例如,据我所知,内置类型的元组(内置类型的元组和此类元组的元组)将属于此类别. 这样做的动机是,如果可能的话,我想传输原始字节.

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_traitstype_trait组合在编译时可以告诉什么(原则上)可以用memcpy代替赋值?

我尝试了对我认为应该满足该条件的类型起作用的方法,令我惊讶的是,唯一适合该行为的条件不是std::is_trivially_assignable而是std::trivially_destructible. 从某种程度上讲,这是有道理的,但令我感到困惑的是,为什么其他一些选择不适用于预期的情况.

我知道可能没有防弹方法,因为人们总是可以编写一个有效的可复制的类,而该类不能检测"为可复制的,但是我正在寻找一种适用于简单直观情况的类.

#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>>{}), "");
}


当然,我认为元组应该是可复制的不是基于标准,而是基于常识和惯例.也就是说,因为通常这样就可以了:

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>{}
    >
{};

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

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

不幸的是,

仍将被报告为不可复制且不可复制.

解决方案

正确的测试实际上是std::is_trivially_copyable,它允许使用memcpy来制作新对象和修改现有对象.

对于直觉告诉您memcpy应该可以的类型,这些类型返回false可能会让您感到惊讶,但是它们并不是在撒谎.在这些情况下,标准确实使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赋值运算符是用户定义的,因此并非易事.


1 实际上,我认为clang,gcc和msvc都是错误的,但是如果它满足std::_is_trivially_copy_assignable的要求,那将无济于事,因为TriviallyCopyable要求复制构造函数(如果不是)删除,是微不足道的,而不是TriviallyCopyAssignable特性.是的,他们是不同的.

如果不是用户提供的,则X类的复制/移动赋值运算符很简单 和...

vs

is_assignable_v<T, const T&>为true,并且分配由 众所周知,is_assignable不会调用任何琐碎的操作.

pair<double, double>的副本分配运算符调用的操作是单个double的分配, 很简单.

不幸的是,可复制的的定义依赖于第一个,而pair则失败.

一个普通可复制的类是一个类:

  • 每个副本构造函数,move构造函数,copy赋值运算符和move赋值运算符要么被删除,要么不重要
  • 具有至少一个未删除的副本构造函数,move构造函数,副本分配运算符或move赋值运算符,并且
  • 具有一个普通的,未删除的析构函数.

I would like to know what type introspection I can do to detect types that assignable by simply raw memory copy?

For example, as far I understand, built-in types tuples of built-in types and tuple of such tuples, would fall in this category. The motivation is that I want to transport raw bytes if possible.

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

What type_trait or combination of type_traits could tell at compile time if assignment can be (in principle) replaced by memcpy?

I tried what would work for the types I would guess should fullfil this condition and to my surprise the only one that fit the behavior is not std::is_trivially_assignable but std::trivially_destructible. It makes sense to some level, but I am confused why some other options do not work with the expected cases.

I understand that there may not be a bullet proof method because one can always write a class that effectively is memcopyable, that cannot be "detected" as memcopyable, but I am looking for one that works for the simple intuitive cases.

#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>>{}), "");
}


Of course my conviction that tuple should be memcopyable is not based on the standard but based on common sense and practice. That is, because this is generally ok:

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);


As a proof of principle, I implemented this. I added a couple of conditions related to the size to avoid some possible misleading specialization of 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>{}
    >
{};

This is a very limited workaround because a class like:

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

will still unfortunately be reported as non trivially copyable and non memcopyable.

解决方案

The correct test is in fact std::is_trivially_copyable, which allows use of memcpy for both making a new object and modifying an existing one.

Although you may be surprised that these return false for types where your intuition tells you that memcpy ought to be ok, they are not lying; the Standard indeed makes memcpy undefined behavior in these cases.


In the particular case of std::pair, we can get some insight into what goes wrong:

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

Evidently it isn't trivially copy assignable.1

The pair assignment operator is user-defined, so not trivial.


1I think that clang, gcc, and msvc are all wrong here, actually, but if it satisfied std::_is_trivially_copy_assignable it wouldn't help, because TriviallyCopyable requires that the copy constructor, if not deleted, is trivial, and not the TriviallyCopyAssignable trait. Yeah, they're different.

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

vs

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.

The operations called by pair<double, double>'s copy assignment operator are the assignments of individual doubles, which are trivial.

Unfortunately, the definition of trivially copyable relies on the first, which pair fails.

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.

这篇关于哪种类型特征表明该类型是memcpy可分配的? (元组,一对)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆