1

I have some POD struct foo; suppose it's struct foo { int x; unsigned y; }. I want to be able to compare struct foo's using lexicographic order - by order of their fields of course. That is, I want all of the operators <, ==, >, etc. to work for struct foo's

Can I do this in some generic way, without having decorated my structure definition with any reflection voodoo - and without just spelling out all those operator definitions? Or is the ability to do this too much of a "language reflection dependent" expectation?

Community
  • 1
  • 1
einpoklum
  • 118,144
  • 57
  • 340
  • 684

5 Answers5

5

You can do this in C++1z. Basing on this answer, I prepared the following proof of concept:

struct anything {
    template<class T> operator T()const;
};

namespace details {
template<class T, class Is, class=void>
struct can_construct_with_N:std::false_type {};

template<class T, std::size_t...Is>
struct can_construct_with_N<T, std::index_sequence<Is...>,
        std::void_t< decltype(T{(void(Is),anything{})...}) >>:
                                                             std::true_type
{};
}

template<class T, std::size_t N>
using can_construct_with_N=details::can_construct_with_N<T, std::make_index_sequence<N>>;

namespace details {
template<std::size_t Min, std::size_t Range, template<std::size_t N>class target>
struct maximize: std::conditional_t<
    maximize<Min, Range/2, target>{} == (Min+Range/2)-1,
    maximize<Min+Range/2, (Range+1)/2, target>,
    maximize<Min, Range/2, target>
>{};

template<std::size_t Min, template<std::size_t N>class target>
struct maximize<Min, 1, target>: std::conditional_t<
    target<Min>{},
    std::integral_constant<std::size_t,Min>,
    std::integral_constant<std::size_t,Min-1>
>{};

template<std::size_t Min, template<std::size_t N>class target>
struct maximize<Min, 0, target>:
    std::integral_constant<std::size_t,Min-1>
{};

template<class T>
struct construct_searcher {
    template<std::size_t N>
    using result = ::can_construct_with_N<T, N>;
};

template<class T, std::size_t Cap=4>
using construct_arity = details::maximize< 0, Cap, details::construct_searcher<T>::template result >;

template<typename T>
constexpr auto tie_as_tuple_impl(std::integral_constant<size_t, 1>, T&& t){
    auto&& [a] = t;
    return std::tie(a);
}

template<typename T>
constexpr auto tie_as_tuple_impl(std::integral_constant<size_t, 2>, T&& t){
    auto&& [a,b] = t;
    return std::tie(a,b);
}

template<typename T>
constexpr auto tie_as_tuple_impl(std::integral_constant<size_t, 3>, T&& t){
    auto&& [a,b,c] = t;
    return std::tie(a,b,c);
}

template<size_t S, typename T>
constexpr auto tie_as_tuple(T&& t){
    return tie_as_tuple_impl(std::integral_constant<size_t, S>{}, std::forward<T>(t));
}

}

template<typename T>
constexpr auto tie_as_tuple(T&& t){
    constexpr size_t S = details::construct_arity<std::decay_t<T>>::value;
    return details::tie_as_tuple<S>(std::forward<T>(t));
}

Now, you can use tie_as_tuple to create a tuple that has all the operators you asked for already defined, in the way you asked for.

demo

Note that I had to prepare several overloads of tie_as_tuple_impl, one for each number of elements in a struct, but that scales linearly for the number of struct elements.


In C++14 there's magic_get that could allow similar solution, but it has its caveats, see here for more information.

Community
  • 1
  • 1
krzaq
  • 16,240
  • 4
  • 46
  • 61
  • This is taking me a while to decipher, but +1 already for the type trait which "counts" the number of elements in the struct - I still can't believe that actualy works... Also, nitpick: it's "arity" rather than "airity". – einpoklum Nov 12 '16 at 18:10
  • @einpoklum that part is stolen from Yakk's implementation (linked). I thought it was a typo too, but at this moment I'm busy seeing if I can make this work with structs with defined constructors (right now this is getting 0 and failing miserably, despite `can_construct_with_N` returning true type). – krzaq Nov 12 '16 at 18:12
2

Can I do this in some generic way, without having decorated my structure definition with any reflection voodoo - and without just spelling out all those operator definitions?

No, there's no way to achieve such in a generic way with the current c++ standard.

I don't even know what you mean with "reflection voodo" since the standard doesn't support type reflection (yet).

And even if so in future, I have doubts that operations like list in lexicographical order would be available in first place.


Or is the ability to do this too much of a "language reflection dependent" expectation?

Probably yes. You may try with a language like c#, wich has reflection, it'll still be tricky to provide a generic operator implementation though.

πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
1

There is currently no shortcut for something like

auto operator < (const foo &a, const foo &b) {
    return std::tie(a.x, a.y) < std::tie(b.x, b.y);
}

in standard C++ (and in Boost afaics).

As this is indeed needless and error-prone typing, Defaulted comparison operators have been proposed, but not yet added to standard C++ (as of the current draft for C++17).

Baum mit Augen
  • 49,044
  • 25
  • 144
  • 182
1

As of C++20, it can be done simply by adding a defaulted spaceship-operator to the class,

struct foo
{
    //...

    auto operator<=>(foo const&) = default;
};

I guess that anyone interested knows that by now, but nevertheless it might be useful as an answer to the question.

davidhigh
  • 14,652
  • 2
  • 44
  • 75
0

You can't do that in standard C++11 or C++14.

You could consider having some program or script generating both the concerned struct-s and their compare function. Perhaps use some external preprocessor like GPP or m4 (or write your own C++ generator). Qt moc might be inspirational.

Or you might consider having some compiler plugin (if using GCC: coded in C++, or in MELT; if using Clang: coded in C++) to help the job. That would require perhaps several weeks of work (because C++ compilers are very complex beasts) so is worthwhile only for large programs.

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547