Меня интересует программное определение, является ли 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 .
Соответствующий раздел о выражении удаления , когда ему присваивается тип класса, читает [expr.delete] стр.1:
[...] Если тип класса, операнд [ delete-expression ] контекстно неявно преобразуется в указатель на тип объекта, delete-expression 's
результат имеет тип void
.
Обратите внимание, что void *
не является "указателем на объект". Здесь мы находим, что результат decltype(delete <blah>)
равен void
: это не зависимое выражение. Тем не менее, я думаю, что для SFINAE нас просто интересует, правильно ли это сформировано.
Преобразование выполняется в соответствии с [conv] стр.5:
Выражение e класса E, фигурирующее как [операнд delete-expression ]:
называется контекстно неявно преобразованным в указанный тип T и
правильно сформирован тогда и только тогда, когда e может быть неявно преобразован в тип
T, который определяется следующим образом: E ищется для преобразования
функции, тип возврата которых cv T или ссылка на cv T, такая что T
разрешено контекстом. Должен быть ровно один такой T.
Поскольку T
должен быть указателем на объект, мы видим, что класс A
соответствует критериям преобразования (и поэтому может быть удален), предоставляя единственную функцию преобразования в указатель на объект, тогда как B
, предоставляя две такие функции, не имеет. Таким образом, попытка удалить объект B должна привести к неправильному выражению. Это то, что static_assert
s в приведенном выше коде должны обеспечить.
Возможный альтернативный подход: можно ли проверить объект на «конвертируемость в некоторый указатель на тип объекта»?
Продолжаем, вот определение недопустимого выражения с целью подстановки типа [type.deduct] стр.8:
Если подстановка приводит к неверному типу или выражению, введите
вычет не удается. Недопустимый тип или выражение - это то, что
плохо сформирован, с необходимостью диагностики, если написано с использованием
замещенные аргументы. [...] Только недопустимые типы и выражения в непосредственном контексте типа функции и ее типов параметров шаблона могут привести к ошибке вывода. [Примечание: оценка замещенных типов и выражений может привести к побочным эффектам, таким как создание специализаций шаблонов классов и / или специализаций шаблонов функций, генерация неявно определенных функций и т. Д. Такие побочные эффекты отсутствуют в «непосредственном» контекст »и может привести к тому, что программа будет плохо сформирована. - конец примечания]
Насколько я могу судить, поскольку удаление объекта B
неправильно сформировано (с необходимостью диагностики), выражение decltype(delete <b>)
должно быть недопустимым, что приводит к срабатыванию SFINAE. Deletable затем заканчивается наследованием от false_type, и мы золотые.
С Клангом, похоже, так и есть. С 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 прав.
For полнота, другие возможные причины, по которым объект не может быть удален:
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()
для D
проходит для GCC, однако случаи C
и E
завершаются неудачей.
main.cpp: In instantiation of ‘struct Deletable<C>’:
main.cpp:31:32: required from here
main.cpp:11:76: error: ‘C::~C()’ is private within this context
struct Deletable<T, void_t<T, decltype(delete declval<T>())> > : true_type { };
^
main.cpp:14:5: note: declared private here
~C();
^
main.cpp: In function ‘int main()’:
main.cpp:31:5: error: static assertion failed: C objects are deletable but shouldn't be because destructor is private.
static_assert(!Deletable<C>::value, "C objects are deletable but shouldn't "
^~~~~~~~~~~~~
main.cpp:31:20: error: ‘C::~C()’ is private within this context
static_assert(!Deletable<C>::value, "C objects are deletable but shouldn't "
^~~~~~~~~~~~
main.cpp:14:5: note: declared private here
~C();
^
main.cpp: In instantiation of ‘struct Deletable<E>’:
main.cpp:35:32: required from here
main.cpp:11:76: error: ‘static void E::operator delete(void*)’ is private within this context
struct Deletable<T, void_t<T, decltype(delete declval<T>())> > : true_type { };
^
main.cpp:27:10: note: declared private here
void operator delete(void*);
^~~~~~~~
main.cpp:35:5: error: static assertion failed: E objects are deletable but shouldn't be because deallocation function is inaccessible.
static_assert(!Deletable<E>::value, "E objects are deletable but shouldn't "
^~~~~~~~~~~~~
main.cpp:35:20: error: ‘static void E::operator delete(void*)’ is private within this context
static_assert(!Deletable<E>::value, "E objects are deletable but shouldn't "
^~~~~~~~~~~~
main.cpp:27:10: note: declared private here
void operator delete(void*);
^~~~~~~~