Можно ли создавать стандартные шаблоны контейнеров с неполными типами?

Иногда полезно создать экземпляр стандартного контейнера с неполным типом для получения рекурсивной структуры:

struct multi_tree_node { // Does work in most implementations
    std::vector< multi_tree_node > child;
};

struct trie_node { // Does not work in most implementations
    std::map< char, trie_node > next;
};

Это работает, потому что контейнеры не имеют членов типа value_type или функций-членов, которые передают или возвращают любые объекты value_type по значению. Стандарт, похоже, не очень много говорит о неполных аргументах шаблона, но есть один бит в С++ 11 §17.6.4.8 [lib.res.on.functions], "требования к другим функциям":

В частности, эффекты undefined в следующих случаях:... если неполный тип (3.9) используется в качестве аргумента шаблона при создании экземпляра компонента шаблона, если это специально не разрешено для этого компонента.

Означает ли это, что приведенные выше конструкции незаконны, даже если экземпляры не находятся в блочной области? Подходит ли это к "операциям над типами, используемыми для создания стандартных компонентов шаблона библиотеки" (также 17.6.4.8)? Или реализация библиотеки запрещена для выполнения экземпляров шаблонов, которые могут быть неудачными для неполных типов, когда все требуемые экземпляры успешно выполняются?

Изменить: Поскольку только функции могут вызывать и создавать экземпляры других функций, ограничение "операций с типами..." с теми, которые находятся в области блока, похоже, будет содержать содержимое функций-членов более строгому требованию, чем содержимое подписи и определения классов членов. В конце концов, это не имеет смысла делать что-либо с помощью multi_tree_node до тех пор, пока тип не будет завершен. И это распространяется на std::unique_ptr, который явно поддерживает аргумент неполного типа, даже если он используется в области блока.

Изменить 2: Служит мне правильно, чтобы не потрудиться протестировать пример trie_node - и я даже пробовал его раньше. Это так же, как пример обрыва в статьи, который @Ise связан. Тем не менее, хотя статья кажется само собой разумеющейся, что "ничего подобного не могло бы работать", решение кажется мне простым - std::map Внутренний tree_node класс должен быть шаблоном без члена, а не членом класса non-template.

Во всяком случае, эта статья очень хорошо устанавливает намерение дизайна, поэтому я думаю, что моя проблема в том, что она находится под подзаголовком "требований к функциям", это только так.

Ответ 1

Лично я чувствую, что формулировка, созданная в 17.6.4.8/2, немного неоднозначно, но согласно в этой статье, стандартное намерение, похоже, не позволяет использовать рекурсивный тип данных, используя стандартные контейнеры.

В соответствующей заметке VC2005 выдает ошибку для class C { std::deque< C > x; };, пока он компилируется class C { std::vector< C > x; };...
Однако, по моему мнению, это ограничение предназначено только для расширения свобода внедрения стандартных контейнеров. Так, как упоминал Kerrek SB, могут быть контейнеры, которые позволяют рекурсивной структуры данных и Boost.Container похоже, предоставляет этот объект.

Ответ 2

Здесь моя попытка интерпретации:

Стандарт просто говорит, что вы не должны этого делать, хотя любая конкретная реализация может не иметь проблем с поддержкой такой конструкции. Но представьте, например, если кто-то хотел написать оптимизацию "малого вектора", в которой вектор всегда содержит пространство, например, для пяти элементов. Сразу же у вас будут проблемы, потому что у вас будет самореферентный тип. Это было бы проблемой, даже если бы вектор использовал какое-то статическое ветвление в зависимости от размера типа значения.

Поэтому, чтобы не исключать возможности внедрения таких конструкций, стандарт просто говорит, что вы должны использовать только полные типы. Другими словами, тот факт, что большинство контейнеров содержат только ссылки или указатели на тип значения, является детальностью реализации, а не стандартным требованием.

Просто пояснить это: если вы определяете свой собственный шаблон класса, вполне возможно его спроектировать таким образом, чтобы он явно поддерживал неполные типы. Примером стандарта является std::unique_ptr, который полностью удовлетворяет параметру неполного типа T[] (или даже void).