Home > Mobile >  C Separate different instances of a class at compile time
C Separate different instances of a class at compile time

Time:08-01

What are available options to separate different instances of a class at compile time?

I basically want the following code snippet to compile without errors:

struct equal_ret_t {};
struct different_ret_t {};

struct Foo
{
    Foo() = default;
    // ... add relevant code here
};

constexpr auto eval(Foo const& a, Foo const& b)
{
    // return equal_ret_t for same instances,
    // different_ret_t for different instances
}

int main()
{
    Foo a;
    Foo b;
    static_assert(std::is_same_v<decltype(eval(a,a)),equal_ret_t>);
    static_assert(std::is_same_v<decltype(eval(b,b)),equal_ret_t>);
    static_assert(std::is_same_v<decltype(eval(a,b)),different_ret_t>);
}

What is the required code to add to the Foo class and/or the eval function?

Are there any solutions approved by the current C 20/23 standard? If not, can the solution made portable across major compilers?

CodePudding user response:

You will not be able to achieve the exact syntax you expect to use, but here's something that may be close enough for your purposes:

#include <type_traits>

struct Foo {
  constexpr bool operator==(const Foo &other) const {
    return this == &other;
  }
};

struct equal_ret_t {};
struct different_ret_t {};

template <bool Equal>
using ret_t = std::conditional_t<Equal, equal_ret_t, different_ret_t>;

int main() {
  Foo a;
  Foo b;
  static_assert(std::is_same_v<ret_t<a == a>, equal_ret_t>);
  static_assert(std::is_same_v<ret_t<b == b>, equal_ret_t>);
  static_assert(std::is_same_v<ret_t<a == b>, different_ret_t>);
}

Try it on Compiler Explorer

CodePudding user response:

If you're willing to make Foo a class template, one way of achieving the desired syntax is using non-template friend injection*.

The following program introduces stateful metaprogramming through a user-defined deduction guide to conceal the fact that a and b are two different types:

#include <type_traits>

namespace detail {

template <std::size_t N>
struct tag {
  friend auto get(tag);

  template <class = tag>
  struct set : std::true_type {
    friend auto get(tag) {}
  };
};

template <std::size_t N = 0>
constexpr auto size(auto const& value) {
  if constexpr (requires { get(tag<N>{}); }) {
    return size<N   1>(value);
  } else {
    return N;
  }
}

}  // namespace detail

struct equal_ret_t {};
struct different_ret_t {};

template <std::size_t N>
struct Foo {
  Foo() = default;

  static_assert(typename detail::tag<N>::set{});
};

template <std::size_t N = detail::size([] {})>
Foo() -> Foo<N>;

constexpr auto eval(auto const& a, auto const& b) {
  return std::conditional_t<std::is_same_v<std::remove_cvref_t<decltype(a)>,
                                           std::remove_cvref_t<decltype(b)>>,
                            equal_ret_t, different_ret_t>{};
}

int main() {
  Foo a;
  Foo b;
  static_assert(std::is_same_v<decltype(eval(a, a)), equal_ret_t>);
  static_assert(std::is_same_v<decltype(eval(b, b)), equal_ret_t>);
  static_assert(std::is_same_v<decltype(eval(a, b)), different_ret_t>);
}

Try it on Compiler Explorer

*You cannot use this technique in a standard-compliant C program since it is compiler implementation-dependent, and because stateful metaprogramming is localized to each translation unit, it will very likely violate ODR as a result.

  • Related