有没有办法检查两个聚合是否相等?

时间:2019-09-14 08:26:51

标签: c++ c++17

让我们假设我有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 {};
};

如果两个聚合具有相同数量的成员字段,相同类型和相同顺序,则将其定义为等效。

例如,UserCar是等效的:3个字段:int, string, string,而UserStudent不是:一个是{{1} },而另一个是int, string, string

此功能的含义很明显,您将能够非常轻松地复制2个无关但相似的聚合。

编辑:聚合来自不同的地方,我无法更改它们,或使它们从同一类或其他任何类继承。我很感兴趣,如果使用所有C ++ 11/17泛型,类型特征,SFINAE magic等,这一切是否有可能实现。

EDIT2:我刚刚发现std::is_layout_compatible()可能符合我的想法,但计划在C ++ 20中发布。

5 个答案:

答案 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)

UserCar从相同的基类继承