Недостатки обобщенных типов в C#

1,00
р.
Хотелось бы задать такой вопрос: неоднократно слышал, что обобщенные типы (generics) в C# менее мощны, нежели шаблоны в C++. Но вот каких-либо доводов в пользу (или против) этого утверждения я не слышал. Действительно ли это так, если да, то в чем именно это проявляется.
P.S. От себя могу лишь добавить, что недавно столкнулся с такой досадной и странной особенностью, что если SomeClassChild — потомок класса SomeClass, то List нельзя преобразовать к List, тогда как SomeClassChild[] к SomeClass[] — можно.
Более того, следующий код также приведет к ошибке:
List lst = new List() lst.Add(new SomeClassChild())

Ответ
Разница на самом деле большая.
Шаблоны C++ -- продвинутая версия макросов. В C# же генерики -- конструкция времени выполнения. Это определяет разницу. Например, такое:
template T add(T lhs, T rhs) { return lhs + rhs }
проходит в C++: во время компиляции шаблона компилятор точно знает тип T и знает, может ли к нему применяться оператор +. Аналогичная конструкция не сработает в C#: компилятор генерирует один код для всех на все случаи, и он не знает, что такое + для произвольных классов. Можно, конечно, обойти проверку при помощи такого трюка:
T add(T lhs, T rhs) { dynamic l = lhs dynamic r = rhs return (T)(l + r) }
-- но эта конструкция выявит отсутствие оператора + во время выполнения, а не компиляции.

Из того, что генерики -- конструкция времени выполнения, выплывают и другие отличия. В частности, в генериках, в отличие от шаблонов C++, нельзя сделать отдельную специализацию для конкретного типа параметров, или для некоторого их набора. (Для шаблонов это называется "частичная/явная специализация".)

Теперь по поводу вашего примера:
List lderived = new List() List lbase = lderived // не компилируется
То, что приведённая строка не компилируется -- правильно. И не должно.
Представьте себе, что этот код таки скомпилировался. Тогда вы сможете написать:
lbase.Add(new SomeClass())
А это уже плохо, потому что теперь в lderived появился экземпляр чужого класса! То, что SomeClassChild[] можно привести к SomeClass[] -- это "тяжёлое наследие" старых версий языка, когда не была реализована ковариантность генериков. (Вставить элемент чужого класса всё равно на получится, вылетит с исключением.)

По поводу
List lst = new List() lst.Add(new SomeClassChild())
-- это должно работать. Перепроверьте!

Следует отметить, что у генериков C# есть и преимущества по сравнению с C++. Например, код генерика один и тот же для всех специализаций (на уровне IL, JIT-компилятор может ради оптимизации сделать несколько разных специализаций на уровне нативного кода). Поэтому вы можете компилировать код, использующий генерик, даже не имея полного исходного текста генерика. Для шаблонов C++ это невозможно: шаблон должен быть перекомпилирован для каждого из типов, с которым он используется. Эта особенность существенно замедляет компиляцию шаблонов в C++ по сравнению с C#, где генерик компилируется лишь один раз.

Дополнение: поскольку в C++ все инстанциации шаблона должны быть известны во время компиляции, это не позволяет динамически решать, с каким типом будет инстанциирован шаблон (что ограничивает семантику программ). У C# инстанциация шаблона происходит во время выполнения, и проблем не возникает. Пример: C#, C++.