【发布时间】:2020-02-19 21:14:11
【问题描述】:
原来的工作代码
当两种类型相同且满足另一个条件时,我有一个具有两个模板参数和一个优化的operator== 的类模板。我的原始代码如下(出于演示目的,我将通用比较返回false 和T1 == T2 返回true 的比较):
template<typename T1, typename T2>
struct my_class
{
// A few hundred LOCs
};
template<typename U1, typename U2>
bool operator==(my_class<U1, U2> const& lhs, my_class<U1, U2> const& rhs)
{
return false;
}
template<typename U>
auto operator==(my_class<U, U> const& lhs, my_class<U, U> const& rhs)
-> std::enable_if_t<some_condition, bool>
{
return true;
}
这个想法是operator== 的第一个重载是默认值,当U1 和U2 是相同类型并且满足some_condition 时,第二个重载是有效的并且被选为更好的匹配。
问题
我最近开始在我的泛型库中实现越来越多的运算符 hidden friends 以避免一些不需要的隐式转换并减少编译器必须在命名空间范围内选择的重载集。
我首先向朋友们尝试了最明显的方法,即按原样移动类模板中的定义并在它们前面加上friend:
template<typename T1, typename T2>
struct my_class
{
// A few hundred LOCs
template<typename U1, typename U2>
friend bool operator==(my_class<U1, U2> const& lhs, my_class<U1, U2> const& rhs)
{
return false;
}
template<typename U>
friend auto operator==(my_class<U, U> const& lhs, my_class<U, U> const& rhs)
-> std::enable_if_t<some_condition, bool>
{
return true;
}
};
这并没有让我感到惊讶,而且我得到了重新定义错误。在this answer 中解释了原因 - 以及标准报价。
一次尝试
我们总是将my_class 与匹配的模板参数进行比较,所以我认为我可以在第一个定义中去掉内部的template<typename U1, typename U2>,但是在第二个定义中它比较棘手,因为单个模板参数用于创建一个更专业的 operator== 重载。另一种解决方案是将重载放在my_class<T, T> 的专业化中,但由于课程很大,我不想复制它的内容,因为几乎所有其他内容都是相同的。我可能会为通用代码引入另一层间接,但我已经有大量的间接了。
我尝试回退到旧的 SFINAE 以确保 T1 和 T2 相同,但失败了:
template<typename T1, typename T2>
struct my_class
{
// A few hundred LOCs
friend auto operator==(my_class const& lhs, my_class const& rhs)
-> bool
{
return false;
}
friend auto operator==(my_class const& lhs, my_class const& rhs)
-> std::enable_if_t<std::is_same<T1, T2>::value && some_condition, bool>
{
return true;
}
};
出于某种原因,我并没有声称完全理解上面的第二个operator== 实际上是格式错误的,但我们可以通过添加一些额外的默认模板参数来解决这个问题:
template<typename T1, typename T2>
struct my_class
{
// A few hundred LOCs
friend auto operator==(my_class const& lhs, my_class const& rhs)
-> bool
{
return false;
}
template<typename U1=T1, typename U2=T2>
friend auto operator==(my_class const& lhs, my_class const& rhs)
-> std::enable_if_t<std::is_same<U1, U2>::value && some_condition, bool>
{
return true;
}
};
这可以按预期编译,但是将 my_class 的两个实例与匹配的 T1 和 T2 进行比较现在返回 false,因为 operator== 的第二个重载比专门化第一。一个有根据的猜测告诉我新的模板层是原因,所以我将模板参数添加到operator== 的第一个重载以及一个 SFINAE 条件,它是另一个的否定,以确保重载不会匹配 T1 和 T2 时有歧义:
template<typename T1, typename T2>
struct my_class
{
// A few hundred LOCs
template<typename U1=T1, typename U2=T2>
friend auto operator==(my_class const& lhs, my_class const& rhs)
-> std::enable_if_t<not(std::is_same<U1, U2>::value && some_condition), bool>
{
return false;
}
template<typename U1=T1, typename U2=T2>
friend auto operator==(my_class const& lhs, my_class const& rhs)
-> std::enable_if_t<std::is_same<U1, U2>::value && some_condition, bool>
{
return true;
}
};
这最终给出了预期的结果,同时也提供了隐藏朋友的好处,但是在可读性和可维护性方面成本有点高。
回到我本来想问的问题
我试图在上面解释我的问题和粗略的解决方案以及我是如何解决的。我的问题是:有没有更好的方法来获得相同的结果(我的代码隐藏的朋友),而不必深入研究我上面强调的所有模板问题?依赖函数模板的内置偏序,而不是像我一样用另一层SFINAE替换它,我能有这样的隐藏朋友吗?
【问题讨论】:
-
摆脱隐式转换而不是试图使这些造成的麻烦更小可能是有意义的。
-
@ÖöTiib 与其说是隐式转换,不如说是更多地了解语言的这个特定角落并考虑我可能错过的内容
-
我可能会在单个 operator== 中使用“if constexpr()”而不是两个重载。
标签: c++ templates c++17 friend-function