使用(递归)约束时有没有办法使用隐藏的朋友?
Is there a way to use hidden friends when using (recursive) constraints?
假设有人 class 想为其定义隐藏的朋友,例如异构比较运算符:
#include <concepts>
template <typename T> struct S;
template <typename C> constexpr bool is_S = false;
template <typename T> constexpr bool is_S<S<T>> = true;
template <typename T>
struct S {
using type = T;
T data;
constexpr S() : data() {}
constexpr explicit S(const T &t) : data(t) {}
template <typename U>
requires
(!is_S<U>) && std::equality_comparable_with<T, U>
friend constexpr bool operator==(const S &s, const U &u) { return s.data == u; }
};
// pre-existing, not hidden friend
template <typename T>
constexpr bool operator==(const S<T> &a, const S<T> &b) { return a.data == b.data; }
上面的代码似乎很合理,但由于 CWG2369 的决议,它无法正常工作——目前仅由 GCC (>=11) 实现。该决议使约束决议有无限递归,例如当使用这样的东西时:
template <typename T>
struct wrapper
{
T value;
};
template <typename T, typename U>
requires std::equality_comparable_with <T, U>
constexpr bool operator==(const wrapper<T>& a, const wrapper<U>& b)
{
return a.value == b.value;
}
using SD = S<double>;
static_assert(std::equality_comparable<wrapper<SD>>); // ERROR, recursive constraints
这种情况下的解决方案应该以非依赖的方式约束S
的operator==
:
template <typename V, typename U>
requires
is_S<V> && (!is_S<U>) && std::equality_comparable_with<typename V::type, U>
friend constexpr bool operator==(const V &v, const U &u) { return v.data == u; }
但现在这不再依赖于特定的 S
专业化,因此会导致重新定义错误:
S<int> s1;
S<double> s2; // ERROR: redefinition of operator==
(Godbolt 以上所有。)
我是不是遗漏了什么,或者上面的递归约束解决方案与隐藏的朋友根本不兼容?
这是一个可能的解决方案:在非模板空基中定义运算符class:
class S_base
{
template <typename V, typename U>
requires
is_S<V> && (!is_S<U>) && std::equality_comparable_with<typename V::type, U>
friend constexpr bool operator==(const V &v, const U &u) { return v.data == u; }
};
然后让S
私下继承它:
template <typename T>
struct S : private S_base
{
using type = T;
T data;
constexpr S() : data() {}
constexpr explicit S(const T &t) : data(t) {}
};
非常感谢 Patrick Palka 建议采用这种方法。
假设有人 class 想为其定义隐藏的朋友,例如异构比较运算符:
#include <concepts>
template <typename T> struct S;
template <typename C> constexpr bool is_S = false;
template <typename T> constexpr bool is_S<S<T>> = true;
template <typename T>
struct S {
using type = T;
T data;
constexpr S() : data() {}
constexpr explicit S(const T &t) : data(t) {}
template <typename U>
requires
(!is_S<U>) && std::equality_comparable_with<T, U>
friend constexpr bool operator==(const S &s, const U &u) { return s.data == u; }
};
// pre-existing, not hidden friend
template <typename T>
constexpr bool operator==(const S<T> &a, const S<T> &b) { return a.data == b.data; }
上面的代码似乎很合理,但由于 CWG2369 的决议,它无法正常工作——目前仅由 GCC (>=11) 实现。该决议使约束决议有无限递归,例如当使用这样的东西时:
template <typename T>
struct wrapper
{
T value;
};
template <typename T, typename U>
requires std::equality_comparable_with <T, U>
constexpr bool operator==(const wrapper<T>& a, const wrapper<U>& b)
{
return a.value == b.value;
}
using SD = S<double>;
static_assert(std::equality_comparable<wrapper<SD>>); // ERROR, recursive constraints
这种情况下的解决方案应该以非依赖的方式约束S
的operator==
:
template <typename V, typename U>
requires
is_S<V> && (!is_S<U>) && std::equality_comparable_with<typename V::type, U>
friend constexpr bool operator==(const V &v, const U &u) { return v.data == u; }
但现在这不再依赖于特定的 S
专业化,因此会导致重新定义错误:
S<int> s1;
S<double> s2; // ERROR: redefinition of operator==
(Godbolt 以上所有。)
我是不是遗漏了什么,或者上面的递归约束解决方案与隐藏的朋友根本不兼容?
这是一个可能的解决方案:在非模板空基中定义运算符class:
class S_base
{
template <typename V, typename U>
requires
is_S<V> && (!is_S<U>) && std::equality_comparable_with<typename V::type, U>
friend constexpr bool operator==(const V &v, const U &u) { return v.data == u; }
};
然后让S
私下继承它:
template <typename T>
struct S : private S_base
{
using type = T;
T data;
constexpr S() : data() {}
constexpr explicit S(const T &t) : data(t) {}
};
非常感谢 Patrick Palka 建议采用这种方法。