SFINAE友好的对象可删除性测试

时间:2018-11-14 04:49:36

标签: c++ gcc template-meta-programming sfinae delete-operator

我对以编程方式确定 delete-expression 对于某些给定对象是否有效感兴趣。似乎应该可以使用SFINAE做到这一点。我提供的代码可以像我期望的Clang 6.0.1一样工作,但是无法使用GCC 7.3.0编译。

两个问题:1)有没有更好的方法可以实现目标,以及2)谁是对的?

#include <type_traits>

using namespace std;

// C++14 variant of void_t
template< typename ...T > struct void_type { typedef void type; };
template< typename ...T > using void_t = typename void_type<T...>::type;

template< typename T, typename = void_t<> > struct Deletable : false_type { };
template< typename T >
struct Deletable<T, void_t<decltype(delete declval<T>())> > : true_type { };
//        Here's the SFINAE'd test  ^^^^^^^^^^^^^^^^^^^

struct A {
    operator signed *() const;
};

struct B {
    operator signed *() const;
    operator unsigned *() const;
};

int main() {
    static_assert(Deletable<A>::value, "Couldn't delete A objects but "
        "should be able to.");
    static_assert(!Deletable<B>::value, "C objects are deletable but "
        "shouldn't be because pointer-to-object is ambiguous.");
    return 0;
}

我尝试回答问题2

tl; dr:我认为Clang是正确的。跳到底部以查看其他一些测试用例。

这就是为什么使用N4140

有关 delete-expression 的相关部分,在给定类类型的情况下为[expr.delete] p.1:

  

[...]如果是类类型, [删除表达式的]操作数被上下文隐式转换为指向对象类型的指针< / strong>。 delete-expression 的   结果的类型为void

请注意,void *不是“对象的指针”。在这里,我们发现decltype(delete <blah>)的结果为void:它不是从属表达式。但是,我认为对于SFINAE,我们只是对它是否格式正确感兴趣。

根据[conv]第5页完成转换:

  

[作为 delete-expression 的操作数]出现的类类型E的表达式e为   据说是上下文隐式转换为指定的类型T,   当且仅当e可以隐式转换为类型时,格式正确   确定的T如下:搜索 E进行转换   返回类型为cv T或引用cv T的函数,使得T   被上下文允许。这样的T应该就是一个。

由于T必须是指向对象的指针,因此我们看到类A通过提供单个转换为对象的指针函数满足了转换标准(因此可以删除)。而B没有提供两个这样的功能。因此,尝试删除B对象应导致格式错误。这就是上面代码中的static_assert所要确保的。

可能的替代方法:是否可以测试对象“可转换为 some 指向对象类型的指针”?

继续,这是一个用于类型替换[type.deduct] p.8的无效表达式的定义:

  

如果替换导致无效的类型或表达式,请键入   推论失败。 无效的类型或表达式将是   格式错误,且需要诊断,如果使用   替换的参数。 [...]只有在函数类型及其模板参数类型的直接上下文中无效的类型和表达式才可能导致推论失败。 [注意:对替换类型和表达式的求值可能导致副作用,例如实例化类模板专业化和/或函数模板专业化,生成隐式定义的函数等。此类副作用不在“立即”中。上下文”,并可能导致程序格式错误。 —尾注]

据我所知,由于删除B对象的格式不正确(需要诊断),decltype(delete <b>)表达式应无效,从而使SFINAE生效。可删除对象最终继承自false_type,我们很高兴。

使用Clang似乎是这种情况。使用GCC,结果如下:

main.cpp: In substitution of ‘template<class T> struct Deletable<T, typename
    void_type<T, decltype (delete (declval<T>)())>::type> [with T = B]’:
main.cpp:24:32:   required from here
main.cpp:24:32: error: ambiguous default type conversion from ‘B’
    static_assert(!Deletable<B>::value, "Shouldn't be able to delete B objects "
                               ^~
main.cpp:24:32: note:   candidate conversions include ‘B::operator int*()
    const’ and ‘B::operator unsigned int*() const’
main.cpp:24:32: error: type ‘struct B’ argument given to ‘delete’, expected
    pointer

我的猜测是,GCC不会将上下文隐式转换视为立即上下文的一部分,因此歧义性将成为硬错误,而不仅仅是类型推导失败。鉴于以上注释中所定义的“直接上下文”的明显意图,我认为转换应在即时上下文中进行。因此,如上所述,我认为Clang是正确的。

出于完整性考虑,对象可能无法删除的其他可能原因是:

class C {
    ~C();
public:
    operator C*() const;
};

struct D {
    ~D() = delete;
    operator D*() const;
};

struct E {
    operator E*() const;
private:
    void operator delete(void*);
};

int main() {
    static_assert(!Deletable<C>::value, "C objects are deletable but shouldn't "
        "be because destructor is private.");
    static_assert(!Deletable<D>::value, "D objects are deletable but shouldn't "
        "be because destructor is deleted.");
    static_assert(!Deletable<E>::value, "E objects are deletable but shouldn't "
        "be because deallocation function is inaccessible.");
    return 0;
}

在Clang中一切正常,有趣的是,static_assert()的{​​{1}}通过了GCC,但是DC的情况失败了。

E

0 个答案:

没有答案