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>);
}
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>);
}
*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.