首页 > 解决方案 > 班级模板中隐藏的朋友模板

问题描述

原始工作代码

我有一个带有两个模板参数的类模板,并且operator==当两种类型相同并且满足另一个条件时进行了优化。我的原始代码如下(出于演示目的,我进行了通用比较 returnfalse和 where T1 == T2return 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==是默认的,当两者U1U2是相同的类型并且some_condition满足时,第二个重载是有效的并且被选为更好的匹配。

问题

我最近开始在我的通用库中实现越来越多的运算符作为隐藏的朋友,以避免一些不需要的隐式转换并减少编译器必须在命名空间范围内选择的重载集。

我首先向朋友们尝试了最明显的方法,即按原样移动类模板中的定义并在它们前面加上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;
    }
};

令我惊讶的是,这不起作用,我得到了重新定义错误。在这个答案中解释了原因 - 以及标准报价。

一次尝试

我们总是my_class与匹配的模板参数进行比较,所以我认为我可以在第一个定义中摆脱内部template<typename U1, typename U2>,但是在第二个定义中它更棘手,因为单个模板参数用于创建更专业的operator==. 另一种解决方案是将重载放在专业化中,my_class<T, T>但由于课程很大,我不想复制它的内容,因为几乎所有其他内容都是相同的。我可能会为通用代码引入另一层间接,但我已经有了大量的间接。

失败了,我试图回退到好的旧 SFINAE 以确保它T1T2相同的:


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_classwith 匹配T1T2现在返回false,因为第二个重载的专业operator==低于第一个。一个有根据的猜测告诉我新的模板层是原因,所以我将模板参数添加到第一个重载operator==和一个 SFINAE 条件,这是对另一个的否定,以确保重载不会与匹配T1T2

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替换它,我能有这样的隐藏朋友吗?

标签: c++templatesc++17friend-function

解决方案


你会接受这个吗?

template<typename T1, typename T2>
struct my_class
{
    friend bool operator==(my_class const& lhs, my_class const& rhs)
    {
        if constexpr (std::is_same_v<T1, T2>) {
            return condition;
        } else {
            return false;
        }
    }
};

推荐阅读