Проверка конструктора по умолчанию с использованием шаблонов классов с помощью std::void_t

#c #c 11 #c 17 #template-meta-programming

#c #c 11 #c 17 #шаблон-метапрограммирование

Вопрос:

Ниже приведен фрагмент, который пытается проверить наличие конструктора по умолчанию во время компиляции. Компиляция этого с

 clang version 11.0.0
Target: x86_64-apple-darwin19.6.0
Thread model: posix
InstalledDir: /usr/local/opt/llvm/bin
 

использование опций clang --std=c 17 -o test_default_ctor test_default_ctor.cpp

 #include <type_traits>

template<typename T, typename = void>
struct has_default_ctor_1 : std::false_type {}; 

template<typename T>
struct has_default_ctor_1<T, std::void_t<decltype(T())>> : std::true_type {}; 

template<typename T, typename = void>
struct has_default_ctor_2 : std::false_type {}; 

template<typename T>
struct has_default_ctor_2<T, std::void_t<decltype(T{})>> : std::true_type {}; 

struct Test1 {
    Test1() = default;
};

struct Test2 {
    Test2() {}
};

struct Test3 {
    Test3() = delete;
};

int main() {
    static_assert(has_default_ctor_1<Test1>::value, "Test has default ctor");
    static_assert(has_default_ctor_2<Test1>::value, "Test has default ctor");
    static_assert(has_default_ctor_1<Test2>::value, "Test has default ctor");
    static_assert(has_default_ctor_2<Test2>::value, "Test has default ctor");
    static_assert(not has_default_ctor_1<Test3>::value, "Test has default ctor");
    static_assert(not has_default_ctor_2<Test3>::value, "Test has default ctor");
}
 

результатом компиляции будет

 test_default_ctor.cpp:33:5: error: static_assert failed due to requirement '!has_default_ctor_2<Test3, void>::value' "Test has default ctor"
    static_assert(not has_default_ctor_2<Test3>::value, "Test has default ctor");
    ^             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.
 

Вопрос в том, почему использование () vs {} для вызова конструктора в специализации шаблона заставляет его работать в одном случае, но не в другом?

Комментарии:

1. Вы хотите знать static_assert(not has_default_ctor_1<Test3>::value, "Test has default ctor"); , почему не произошел сбой?

2. Я хочу знать, почему static_assert(not has_default_ctor_2<Test3>::value, "Test has default ctor"); произошел сбой, то есть почему has_default_ctor2... для этого случая true, когда конструктор по умолчанию удален

3. ну, я запутался. Возможно, более четко разъясните в вопросе, каковы были ваши ожидания. Test1 и Test2 не имеют отношения к вопросу и могут быть удалены, не так ли?

4. конечно, их можно было бы удалить, я оставил их только для того, чтобы показать пример. я поясню. Спасибо!

Ответ №1:

 template<typename T>
struct has_default_ctor_2<T, std::void_t<decltype(T{})>> : std::true_type {}; 
 

Поскольку вы проверяете, правильно ли T{} сформировано, вы разрешаете также типы, которые могут быть инициализированы с помощью агрегатной инициализации. Test3 является агрегатным классом до C 20, поскольку он не имеет пользовательского конструктора. Из [dcl.fct.def.default]/5 [извлечение, выделение мое]:

[…] Функция предоставляется пользователем, если она объявлена пользователем и явно не установлена по умолчанию или не удалена при ее первом объявлении.

Подробное описание агрегатов для различных стандартных версий см., например:

Агрегаты в C 20

Начиная с C 20, в частности, благодаря реализации P1008R1 (запрещать агрегаты с объявленными пользователем конструкторами), большая часть часто неожиданного агрегированного поведения, описанного выше, была устранена, в частности, за счет того, что агрегаты больше не разрешают иметь объявленные пользователем конструкторы, более строгое требование к классу быть агрегатом, чем просто запрещение пользовательских конструкторов.


Комментарии:

1. что изменилось в C 20?

2. Требование к тому, чтобы быть агрегатом, стало более строгим: от не предоставленных пользователем до не объявленных пользователем ctors.

3. действительно, добавление к Test3 чему-то вроде: Test3(int) {} заставляет все работать так, как ожидалось. Спасибо!