【问题标题】:Curiously Recurring Pattern and Sfinae奇怪地重复出现的模式和 Sfinae
【发布时间】:2017-02-15 10:28:09
【问题描述】:

一段时间以来,我一直想通过 SFINAE 和 Curiously Recurring Template Pattern 习语来实现全排序。大致思路如下:

  1. 定义检查关系运算符(<> 等)的模板
  2. 定义定义总排序运算符的基类。
  3. 根据运算符检测定义基类。
  4. 从基类继承。

为简单起见,我在此示例中忽略了 ==!= 运算符。

关系运算符检测

我首先定义类来静态检查该类是否定义了特定的功能。例如,这里我检测到小于运算符的存在,即operator<

template <typename T>
class has_less
{
protected:
    template <typename C> static char &test(decltype(std::declval<C>() < std::declval<C>()));
    template <typename C> static long &test(...);

public:
    enum {
        value = sizeof(test<T>(0)) == sizeof(char)
    };
};

template <typename T>
constexpr bool has_less_v = has_less<T>::value;

总订购量

然后,我定义了从给定运算符实现全排序的类,例如,要从小于运算符定义全排序,我将使用以下内容:

template <typename T>
struct less_than_total
{
    bool operator>(const T &t) { return t < static_cast<T&>(*this); }
    bool operator>=(const T &t) { return !(static_cast<T&>(*this) < t); }
    bool operator<=(const T &t) { return !(t < static_cast<T&>(*this)); }
}; 

基类

然后我定义一个基类,它创建一个 typedef 来通过检测实现的运算符来实现剩余的运算符。

template <bool B, typename T, typename F>
using conditional_t = typename std::conditional<B, T, F>::type;

template <typename T>
using total_ordering = conditional_t<           // has_less
    has_less_v<T>,
    less_than_total<T>,
    conditional_t<                              // has_less_equal
        has_less_equal_v<T>,
        less_equal_total<T>,
        conditional_t<                          // has_greater
            has_greater_v<T>,
            greater_total<T>,
            conditional_t<                      // has_greater_equal
                has_greater_equal_v<T>,
                greater_equal_total<T>,
                symmetric<T>                    // symmetry
            >                                   // has_greater_equal
        >                                       // has_greater
    >                                           // has_less_equal
>;                                              // has_less

继承

所有这些步骤单独工作。但是,当我实际上使用奇怪的重复模式从基类继承时,生成的类只实现了这些运算符之一,并且检测算法失败了。

示例

我已将问题归结为一个包含核心部分的最小示例:运算符检测(has_lesshas_greater)、总排序实现(total)、简化的基类(total) ,以及使用这些关系运算符 (A) 的简单结构。

#include <type_traits>


// DETECTION

template <typename T>
class has_less
{
protected:
    template <typename C> static char &test(decltype(std::declval<C>() < std::declval<C>()));
    template <typename C> static long &test(...);

public:
    enum {
        value = sizeof(test<T>(0)) == sizeof(char)
    };
};


template <typename T>
class has_greater
{
protected:
    template <typename C> static char &test(decltype(std::declval<C>() > std::declval<C>()));
    template <typename C> static long &test(...);

public:
    enum {
        value = sizeof(test<T>(0)) == sizeof(char)
    };
};


// TOTAL ORDERING


template <typename T>
struct less_than_total
{
    bool operator>(const T &t) { return t < static_cast<T&>(*this); }
    bool operator>=(const T &t) { return !(static_cast<T&>(*this) < t); }
    bool operator<=(const T &t) { return !(t < static_cast<T&>(*this)); }
};


template <typename T>
struct symmetry
{};


template <bool B, typename T, typename F>
using conditional_t = typename std::conditional<B, T, F>::type;


template <typename T>
struct total: conditional_t<
        has_less<T>::value,
        less_than_total<T>,
        symmetry<T>
    >
{};


// TEST

struct A: total<A>
{
    bool operator<(const A &r)
    {
        return true;
    }
};



int main(void)
{
    static_assert(has_less<A>::value, "");
    static_assert(has_greater<A>::value, "");
    return 0;
}

理想情况下,这个例子可以编译,但是,我得到:

$ clang++ a.cpp -o a -std=c++14
a.cpp:79:5: error: static_assert failed ""
    static_assert(has_less<A>::value, "");
    ^             ~~~~~~~~~~~~~~~~~~
a.cpp:80:5: error: static_assert failed ""
    static_assert(has_greater<A>::value, "");

不幸的是,基类在继承过程中没有检测到运算符,SFINAE 也没有检测到结果类中的小于或大于运算符。

问题和跟进

我想知道为什么会失败,因为我一直在用奇怪的重复模式进行成员函数检测和成员类型检测没有问题。假设我的代码没有直接问题,是否有任何变通方法可以以这种方式实现总排序?

编辑

我能够使用std::enable_if 实现我想要的一个子集。在这种情况下,唯一简单的答案是根据operator&lt; 实现所有内容,然后从该运算符实现全排序。

template <typename T>
struct total
{
    template <typename U = T>
    typename std::enable_if<has_less<U>::value, bool>::type
    bool operator>(const T &l, const T &r) { return r < t; }
};

如果我仍然想知道为什么我通过 SFINAE 的运算符检测在继承期间失败,但对继承的方法成功。

【问题讨论】:

  • 您希望在该示例中发生什么?
  • @mascoj,理想情况下,它会编译。我将编辑我的问题。

标签: c++ sfinae


【解决方案1】:

这样做的主要问题是A 在实例化has_less&lt;A&gt; 时是一个不完整的类型(在total&lt;A&gt; 实例化为A 的基类期间)——此时,编译器不会'还不知道A 有一个operator &lt;

所以,has_less&lt;A&gt; 用它的value == 0 实例化,并且symmetry&lt;A&gt; 被选为total&lt;A&gt; 的基类——所以A 永远不会得到它的任何其他运算符。

在所有这些决定之后,编译器会看到A::operator &lt; 的定义,并将其添加到A。之后,A 就完成了。

所以我们知道为什么static_assert(has_greater&lt;A&gt;::value, ""); 会失败,但我们不应该期望static_assert(has_less&lt;A&gt;::value, ""); 成功吗?毕竟,现在A 有一个小于运算符。问题是,has_less&lt;A&gt; 已经用不完整的A 实例化,value == 0 - 即使A 已经改变,也没有更新先前实例化的编译时值的机制。所以这个断言也失败了,即使它看起来应该成功。

要证明是这种情况,请复制has_less,将其命名为has_less2,并将静态断言更改为static_assert(has_less2&lt;A&gt;::value, "");。因为has_less2&lt;A&gt; 是在A 得到它的小于运算符之后实例化的,所以这个断言成功了。

使代码成功的一种方法是前向声明A 并声明一个全局operator &lt;,用于比较两个A 对象,以便编译器在A 的基础之前知道该运算符上课了。像这样的:

struct A;
bool operator < (const A &lh, const A& rh);

struct A : total<A> {
    friend bool operator < (const A &lh, const A& rh) {
        return true;
    }
};

我知道这并不是您真正想要的,但是,如果 CRTP 设置自动发生,而派生类中不需要任何特殊调整,那就更好了。但这可能仍然会给您一些洞察力,可以帮助您找到正确的解决方案。我也会考虑更多,如果我想出什么,我会更新这个答案。

还有一件事:比较成员函数应该是const 限定的。喜欢

bool operator>(const T &t) const { ...

这非常重要,并且可以防止在编译以后使用这些类的代码时出现很多不明显的问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-20
    • 2018-11-12
    • 2012-04-30
    • 2013-02-15
    相关资源
    最近更新 更多