有没有办法检查 2 个聚合是否等效?
Is there a way to check if 2 aggregates are equivalent?
假设我有 3 个聚合:
struct User
{
int age {};
std::string name;
std::string address;
};
struct Car
{
int power {};
std::string name;
std::string owner;
};
struct Student
{
std::string name;
std::string address;
std::int age {};
};
我会定义 2 个聚合是等效的,如果它们具有相同数量、相同类型和相同顺序的成员字段。
例如,User
和Car
是等效的:3 个字段:int, string, string
,但User
和Student
不是:一个是int, string, string
,另一个是string, string, int
。
我想说这个功能的含义是显而易见的,您将能够非常轻松地复制 2 个不相关但相似的聚合。
编辑:聚合来自不同的地方,我无法更改它们,或者使它们从同一类继承,或者其他任何东西。我很感兴趣,如果所有 C++11/17 泛型、类型特征、SFINAE 魔法等,这完全是可能的。
编辑2:我刚刚发现了std::is_layout_compatible()
它可能符合我的想法,但它计划在C++20发布。
结构等效性(忽略语义等效性(不是在没有编译器支持的情况下可以检查的功能。
您需要某种类型的基本反射,或者预先打包的东西喜欢 C++20std::is_layout_compatible
.
您的特定情况很有趣,因为所有成员都是公共的,允许您使用 C++17 结构化绑定来获取对成员的引用,尽管您必须知道元素的数量以及它们是否是引用。
template <class T, class T2, class U, class U2>
bool structurally_equivalent_helper(T&& a, T2&& a2, U&& b, U2&& b2) {
return std::is_same_v<decltype(a2), decltype(b2)>
&& ((char*)&a2 - (char*)&a) == ((char*)&b2 - (char*)b);
}
template <class T, class U>
bool structurally_equivalent3(T&& a, U&& b) {
auto&& [a1, a2, a3] = std::forward<T>(a);
auto&& [b1, b2, b3] = std::forward<U>(b);
return structurally_equivalent_helper(a, decltype(a1)(a1), b, decltype(b1)(b1))
&& structurally_equivalent_helper(a, decltype(a2)(a2), b, decltype(b2)(b2))
&& structurally_equivalent_helper(a, decltype(a3)(a3), b, decltype(b3)(b3));
}
要在没有任何额外样板的情况下做到这一点,您需要反思,不幸的是,这还没有C++(不过,它可能会达到 C++23(。
您可以通过向每个对象添加tie
函数来获得所需的大部分内容。
#include <tuple>
#include <string>
struct User
{
int age{};
std::string name;
std::string address;
auto tie() { return std::tie(age, name, address); }
};
struct Car
{
int power{};
std::string name;
std::string owner;
auto tie() { return std::tie(power, name, owner); }
};
struct Student
{
std::string name;
std::string address;
int age{};
auto tie() { return std::tie(name, address, age); }
};
int main() {
auto b1 = User().tie() == Car().tie();
auto b2 = User().tie() == Student().tie(); // compile error
}
magic_get库使它相对容易:
#include <cstddef>
#include <type_traits>
#include <utility>
#include <boost/pfr.hpp>
template <std::size_t ...I, typename F>
constexpr bool all_of_seq(std::index_sequence<I...>, F func)
{
return ((func(std::integral_constant<std::size_t, I>{})) && ...);
}
template <typename A, typename B>
inline constexpr bool is_equivalent_v = []
{
namespace pfr = boost::pfr;
if constexpr (!(sizeof(A) == sizeof(B) && pfr::tuple_size_v<A> == pfr::tuple_size_v<B>))
{
return false;
}
else
{
return all_of_seq(std::make_index_sequence<pfr::tuple_size_v<A>>{}, [&](auto index)
{
constexpr int i = index.value;
return std::is_same_v<pfr::tuple_element_t<i, A>, pfr::tuple_element_t<i, B>>;
});
}
}();
首先,我们确保两个结构具有相同的大小和相同的字段数量:
sizeof(A) == sizeof(B) && pfr::tuple_size_v<A> == pfr::tuple_size_v<B>
然后,我们比较字段类型:
std::is_same_v<pfr::tuple_element_t<i, A>, pfr::tuple_element_t<i, B>>
此解决方案主要等同于@Deduplicator建议的解决方案,但由于magic_get,它不涉及编写样板模板。
此外,此实现不比较字段偏移量(因为我认为它不能在编译时完成(,这使得它不太可靠:如果结构字段上有alignas
,您可能会得到误报。
用法:
#include <iostream>
struct User
{
int age {};
std::string name;
std::string address;
};
struct Car
{
int power {};
std::string name;
std::string owner;
};
struct Foo
{
int x, y;
};
int main()
{
std::cout << is_equivalent_v<User, User> << 'n'; // 1
std::cout << is_equivalent_v<User, Car > << 'n'; // 1
std::cout << is_equivalent_v<User, Foo > << 'n'; // 0
std::cout << is_equivalent_v<Car , Foo > << 'n'; // 0
}
如果您事先知道类型,简单的解决方案是提供您正在使用的所有等效类型的描述。例如:
#include <iostream>
#include <type_traits>
using namespace std;
template<typename... Args>
struct EquivalenceType
{};
template<typename T>
struct EquivalenceClass
{};
template<typename T1, typename T2>
bool AreClassEquivalent()
{
return std::is_same<typename EquivalenceClass<T1>::Type, typename EquivalenceClass<T2>::Type>::value;
}
struct User
{
int age{};
std::string name;
std::string address;
};
struct Car
{
int power{};
std::string name;
std::string owner;
};
template<>
struct EquivalenceClass<User>
{
using Type = EquivalenceType<int, std::string, std::string>;
};
template<>
struct EquivalenceClass<Car>
{
using Type = EquivalenceType<int, std::string, std::string>;
};
int main()
{
cout << AreClassEquivalent<User, Car>() << endl;
}
对于您希望它"等效可比"的每个类,您需要提供等效类模板的专业化。
这样做的一大缺点是保持一致性,例如,在修改用户定义并忘记更新其 EquivalenceClass 之后。
- 检查输入是否不是整数或数字
- 检查值是否在集合p1和p2中,但不在p3中
- 检查 std::shared_ptr<> 的当前底层类型是否为 T
- 在c++中检查长方体是否尽可能快地重叠(无迭代)
- 如何检查线程是否锁定
- C++LDAP检查用户是否是特定组的成员
- 检查TCHAR数组输入是否为带符号整数C++
- 如何检查QList中是否存在值
- 检查函数返回类型是否与STL容器类型值相同
- 检查是否以特定精度给出双精度
- 检查向量是否具有所有可能的字符组合
- 检查注册表项是否链接到(或副本)另一个注册表项
- 地图计数确实很重要,或者只是检查是否存在
- 检查 2 棵树是否具有相同的顺序
- std::next 是否检查我们是否已经在容器的末尾?
- "!" "== 0"是否检查 int 是否是 0 的好做法?
- 我是否检查是否存在带或不带参数的宏函数
- dynamic_cast是否检查被查询对象的type_info对象,或者递归地检查
- 在VS2010中编译c++ 11中的代码时,std::function()是否检查类型
- 如果第二个参数"0",strcmp 是否C++检查字符串中的每个值?