让我们假设我有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 {};
};
如果两个聚合具有相同数量的成员字段,相同类型和相同顺序,则将其定义为等效。
例如,User
和Car
是等效的:3个字段:int, string, string
,而User
和Student
不是:一个是{{1} },而另一个是int, string, string
。
此功能的含义很明显,您将能够非常轻松地复制2个无关但相似的聚合。
编辑:聚合来自不同的地方,我无法更改它们,或使它们从同一类或其他任何类继承。我很感兴趣,如果使用所有C ++ 11/17泛型,类型特征,SFINAE magic等,这一切是否有可能实现。
EDIT2:我刚刚发现std::is_layout_compatible()
可能符合我的想法,但计划在C ++ 20中发布。
答案 0 :(得分:1)
在没有编译器支持的情况下,无法检查结构等效性(忽略语义等效性)。
您需要某种类型的基本反射,或一些预先包装好的“ C++20 std::is_layout_compatible
”。
由于所有成员都是公开的,因此您的特定情况很有趣,尽管您必须知道元素的数量以及它们是否为引用,但允许您使用C ++ 17 structured bindings获取对成员的引用。
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));
}
答案 1 :(得分:1)
要做到这一点而无需任何额外的模板,您需要反射,但不幸的是,这在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
}
答案 2 :(得分:1)
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
}
答案 3 :(得分:1)
如果您事先知道类型,那么简单的解决方案就是提供您正在使用的所有等效类型的描述。例如:
#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模板的特殊化。
例如,这样做的一大缺点是保持一致性。修改用户定义并忘记更新它的EquivalenceClass之后。
答案 4 :(得分:-3)
让User
和Car
从相同的基类继承