std::is_constructible для типа с закрытым деструктором

Каков ожидаемый результат для 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 на coliru).

Это требуемое поведение по стандарту? Если да, то есть ли способ проверить, имеет ли тип конкретный конструктор, независимо от спецификатора доступа в деструкторе?


person ComicSansMS    schedule 22.01.2015    source источник
comment
Связанный: groups.google.com/a/ isocpp.org/forum/#!topic/std-discussion/   -  person Caramiriel    schedule 22.01.2015
comment
@Caramiriel К сожалению, это не решает проблему напрямую. is_destructible явно зависит от доступности деструктора, что здесь не так.   -  person Columbo    schedule 22.01.2015


Ответы (2)


FD C++14 определяет is_constructible следующим образом:

Учитывая следующее объявление функции:

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

предикатное условие для специализации шаблона is_constructible<T, Args...> должно быть выполнено тогда и только тогда, когда следующее определение переменной будет корректным для некоторой придуманной переменной t:

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

Проверка доступа выполняется, как будто в контексте, не связанном с T и любым из Args. Учитывается только правильность непосредственного контекста инициализации переменной. [ Примечание: оценка инициализации может привести к побочным эффектам, таким как создание экземпляров специализаций шаблонов классов и специализаций шаблонов функций. , генерация неявно определенных функций и так далее. Такие побочные эффекты не относятся к «непосредственному контексту» и могут привести к неправильному построению программы. —конец примечания ]

Теперь вопрос по существу сводится к «Находится ли вызов деструктора в непосредственном контексте инициализации переменной?» [класс.дтор]/11:

Деструктор вызывается неявно

  • для построенного объекта со статической продолжительностью хранения (3.7.1) при завершении программы (3.6.3),
  • для сконструированного объекта с автоматической продолжительностью хранения (3.7.3) при выходе из блока, в котором создается объект (6.7),
  • для построенного временного объекта, когда его время жизни заканчивается (12.2).

В каждом случае контекст вызова является контекстом построения объекта.

Таким образом, вызов деструктора находится в контексте конструкции (которая предположительно является синонимом инициализации здесь), что подразумевает, что он рассматривается, и приводит к тому, что трейт возвращает false. явно-непосредственный контекст?), но интуитивно я ожидаю, что соответствующая реализация пометит выражение NotDestructible() как неправильно сформированное - либо дружественное к SFINAE, либо нет (предпочтительно первое). Однако никогда не был правильно сформирован.
Clang с libc++, libstdc++ и GCC действительно говорят, что это недопустимо, Подходит для SFINAE.


Если да, то есть ли способ проверить, имеет ли тип конкретный конструктор, независимо от спецификатора доступа в деструкторе?

Как насчет использования 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;
};

Демо. Непротиворечивые трейты можно легко установить: возьмите трейт is_only_constructible и объедините его с is_destructible (очевидно, что последний возвращает false в сочетании с приватными деструкторами).

person Columbo    schedule 22.01.2015
comment
Мне интересно: если намерение Комитета состояло в том, чтобы is_constructible<T, Args...>::value возвращало true, даже когда деструктор T недоступен (что, кажется, является тем, что вы утверждаете), почему бы не указать поведение этого признака в терминах выражения new? - person Andy Prowl; 22.01.2015
comment
@AndyProwl Я не это имею в виду. Я думаю, что комитет не рассмотрел это дело должным образом, и поэтому текст недоопределен. - person Columbo; 22.01.2015
comment
Я понимаю. Тогда я, вероятно, неправильно понял это утверждение: насколько я вижу, вызов деструктора не находится в непосредственном контексте инициализации и поэтому не должен рассматриваться. - person Andy Prowl; 22.01.2015
comment
Итак, вы утверждаете (поправьте меня, если я ошибаюсь), что текущая формулировка подразумевает, что is_constructible должно дать true, но это не входило в намерения Комитета. Я правильно понял? - person Andy Prowl; 22.01.2015
comment
@AndyProwl Ладно, извини, серьезно. Я еще не пил кофе. Я хотел сказать, что я думаю, что GCC и VC++ правы, и трейт должен возвращать false, потому что вызов деструктора находится в рассматриваемом контексте. Обновил мой ответ. - person Columbo; 22.01.2015
comment
@AndyProwl Я не уверен в намерениях. Вопрос EWG, возможно, был бы уместным, не так ли? - person Columbo; 22.01.2015
comment
А как насчет патологических new перегрузок, могут ли они вызывать проблемы? - person Yakk - Adam Nevraumont; 22.01.2015
comment
@Yakk Хорошо, а как насчет размещения new? Это может быть перегружено? - person Columbo; 22.01.2015
comment
@Columbo: Может быть, хотя я настолько привык к тому, что Стандарт был супер-неясным, что я как бы перестал рассуждать о том, что подразумевает фактическая формулировка, и, скорее, начал думать о том, каковы были более вероятные намерения Комитета при его написании. Конечный результат тот же (я не знаю, и я могу ошибаться), но, по крайней мере, я сэкономлю нервную жизнь, не перескакивая с одного неоднозначного абзаца на другой :) - person Andy Prowl; 22.01.2015
comment
Решение is_only_constructible не работает корректно с MSVC2017 15.7.5, в IDE при наведении курсора мыши вроде работает, но при компиляции и запуске всегда возвращает false. - person Rangel Reale; 12.07.2018

Цитируя абзац [meta.unary.prop]/7 стандарта С++ (проект N4296):

Учитывая следующее объявление функции:

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

предикатное условие для специализации шаблона is_constructible<T, Args...> должно быть выполнено тогда и только тогда, когда следующее определение переменной будет правильным для некоторой выдуманной переменной t:

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

Другими словами, is_constructible<T, Args...>::value дает false, если деструктор недоступен.

person Andy Prowl    schedule 22.01.2015
comment
Так что насчет второй части вопроса: есть ли способ проверить, имеет ли тип конкретный конструктор, независимо от спецификатора доступа в деструкторе? - person ComicSansMS; 22.01.2015
comment
@ComicSansMS: боюсь, я не могу ответить на эту часть (по крайней мере, не сразу), хотя я подозреваю, что ответ отрицательный. - person Andy Prowl; 22.01.2015
comment
Вот чего я боялся. Проверить наличие обычной функции-члена не так сложно, но конструкторы сложны. Думаю, это можно считать недосмотром type_traits. - person ComicSansMS; 22.01.2015
comment
@ComicSansMS В моем ответе есть возможный обходной путь. - person Columbo; 22.01.2015
comment
@ComicSansMS: Не знаю почему, но я почему-то все думал об обнаружении приватного/защищенного конструктора, тогда как проблема явно в спецификаторе доступа деструктора. Решение Коломбо действительно то, о чем я должен был подумать. - person Andy Prowl; 22.01.2015