std::is_constructible 具有非 public 析构函数的类型

std::is_constructible on type with non-public destructor

std::is_constructible 在具有私有或受保护析构函数的类型上的预期结果是什么?

例如,即使只有朋友可以释放它,我仍然可以在堆上构造这样一个对象:

#include <type_traits>

class Foo
{
    friend void freeFoo(Foo*);
public:
    Foo()
    {}
private:
    // Destructor is private!
    ~Foo()
    {}
};

void freeFoo(Foo* f)
{
    delete f;  // deleting a foo is fine here because of friendship
}

int main()
{
    Foo* f = new Foo();
    // delete f;   // won't compile: ~Foo is private
    freeFoo(f);    // fine because of friendship


    if(!std::is_constructible<Foo>::value)
    {
        std::cout << "is_constructible failed" << std::endl;
    }
}

is_constructible 的最终检查在 gcc 和 Visual C++ (gcc demo on coliru) 上都会失败。

这是标准要求的行为吗?如果是这样,有什么方法可以检查该类型是否具有特定的构造函数,而不考虑析构函数上的访问说明符?

引用 C++ 标准(草案 N4296)的第 [meta.unary.prop]/7 段:

Given the following function declaration:

template <class T>
add_rvalue_reference_t<T> create() noexcept;

the predicate condition for a template specialization is_constructible<T, Args...> shall be satisfied if and only if the following variable definition would be well-formed for some invented variable t:

T t(create<Args>()...);

换句话说,如果析构函数不可访问,is_constructible<T, Args...>::value 会产生 false

C++14 FD定义is_constructible如下:

Given the following function declaration:

template <class T>
add_rvalue_reference_t<T> create() noexcept;

the predicate condition for a template specialization is_constructible<T, Args...> shall be satisfied if and only if the following variable definition would be well-formed for some invented variable t:

T t(create<Args>()...);

Access checking is performed as if in a context unrelated to T and any of the Args. Only the validity of the immediate context of the variable initialization is considered. [ Note: The evaluation of the initialization can result in side effects such as the instantiation of class template specializations and function template specializations, the generation of implicitly-defined functions, and so on. Such side effects are not in the “immediate context” and can result in the program being ill-formed. —end note ]

现在问题基本上减少到 "Is the destructor call in the immediate context of the variable initialization?" [class.dtor]/11:

A destructor is invoked implicitly

  • for a constructed object with static storage duration (3.7.1) at program termination (3.6.3),
  • for a constructed object with automatic storage duration (3.7.3) when the block in which an object is created exits (6.7),
  • for a constructed temporary object when its lifetime ends (12.2).

In each case, the context of the invocation is the context of the construction of the object.

因此析构函数调用是在构造的上下文中(这里大概是初始化的同义词),这意味着它被考虑并导致特征 return false.
我认为这是未指定的(例如直接与非明确直接上下文?),但直觉上我希望符合规范的实现将表达式 NotDestructible() 标记为格式错误 - SFINAE 友好与否(最好是前者).不过,从来没有合式。
Clang with libc++, libstdc++ and GCC do say that it's invalid, SFINAE-friendly.


If so, is there any way to check whether the type has a specific constructor, regardless of the access specifier on the destructor?

使用 new 怎么样?

template <typename T, typename... Args>
class is_only_constructible
{
    template <typename, typename=void> struct test : std::false_type {};
    template <typename U>
    struct test<U, decltype(void(new U(std::declval<Args>()...)))> : std::true_type {};

public:
    static constexpr bool value = test<T>::value;
};

Demo。可以很容易地建立一致的特征:采用 is_only_constructible 特征并将其与 is_destructible 组合(显然后者 returns false 与私有析构函数组合时)。