Templates: statically typed or dynamic? - c++

C++ is considered statically typed. I understand that.
I don't understand how that applies to templates.
Here is a simple example of a type that cannot be determined at compile time:
template <typename... t>
struct foo {
using type = typename foo<t..., t...>::type;
};
foo<int>::type x; // type of x cannot be determined without running meta-program
I presume there are cases where it's impossible to detect type errors without solving the halting problem.
So my question is, why aren't templates considered dynamically typed?

Static/dynamic typing typically refers to the behavior at runtime of the final compiled program, not of the meta-program. Since foo<int>::type is resolved by the time you reach runtime of the final compiled program, it's considered statically typed.
As for the template metaprogram, one could consider it to be using duck typing, which is a kind of dynamic typing. Note, however, that there are still static types (in pre-C++11) - the number of template arguments on a template can be considered a meta-type for a meta-function that produces a concrete type (which is a value as far as the meta-program is concerned).
By comparison, in Haskell, they have a concept of a hierarchy of types. You have typical types - things like functions, integers, etc. Then you have 'kinds', which describe types and meta-functions on types. For example, the Haskell kind * -> * -> * could refer to a mapping of keys to values, much like a template<typename Key, typename Value> class Map in C++. Any determination on whether the language is statically or dynamically typed then would have to refer to which level of the hierarchy you're referring to. Historically, C++ templates were never really thought of as meta-programs when they were first designed, so this kind of terminology isn't as widely used in C++, but the same concepts can still be applied.

Related

Equivalent to `<T extends MyClass>` in C++

In Java you can define a generic type, that should interhit from anoter, by <T extends MyClass> void myMethod(T item)
Is there an equivalent in cpp? I tried template<class T : Draw_Shape> class MyClass, but dont work.
When reading below, please be aware that I am not a Java programmer. My knowledge of Java is almost entirely abstract, and possibly out of date. So if Java implemented reification of generics in the last version or two I don't know about it.
So Java Generics and C++ templates share some common syntax and uses, but they are very different things under the hood.
Java Generics are a wrapper of automatically written casts and compile time type checks around a single core type.
C++ templates on the other hand generate new unrelated types for each set of template arguments.
The extends MyClass syntax in Java does two things. First, it permits that "core" type of the generic to know that the T is not merely an Object, but actually a subclass of some interface. This is required for the "core" type to use methods safely (without can-fail-at-runtime dynamic casts).
In C++ this doesn't happen because there is no "core" type generated by a template instantiation. Each template instantiation is independently compiled, and so knows if the operations are valid or not.
The second thing it does is it gives type errors when the wrong type is passed to the generic. The third thing it does is it allows Java to check the Generic code for validity prior to it being instantiated.
For the second, C++ can use concepts (if your compiler is new enough) or use a technique known as SFINAE that is equally powerful, but syntactically awful and honestly its ability to solve this problem was an accident of language development (it is accidentally Turing complete).
For the third, checked templates in C++, it has been proposed many times, but keeps running into compile time performance issues. So there isn't a way to do it in C++, short of instantiating a template.
Solutions:
Do nothing:
No, seriously. Embrace duck typing and don't constrain template parameters. The only big downside is ugly error messages, and rarely "same named operation has different meanings".
static_assert:
template<class T> class MyClass{
static_assert(std::is_base_of_v<Draw_Shape,T>);
};
this generates clean error messages. There are some downsides in that other code cannot test if MyClass<X> is a valid instantiation without a hard compiler error.
SFINAE:
template<class T,
std::enable_if_t<std::is_base_of_v<Draw_Shape,T>, bool> =true
>
class MyClass{
};
note that the =true does not compare the test to the value true. It is not ==true. What is going on here is ridiculously complex and annoying; using SFINAE for this purpose is a hack, and this is just a monkey-see monkey-do way to make it clean.
Concepts:
template<class T> requires std::is_base_of_v<Draw_Shape,T>
class MyClass{
};
or
template<std::is_derived_from<Draw_Shape> T>
class MyClass{
};
note that concepts requires a modern C++ complier and std library.

Is all use of templates in C++ metaprogramming?

I'm trying to understand what metaprogramming is general and what it is in C++ in particular. If I search for c++ metaprogramming I do get tutorials of template metaprogramming (TMP), but no explanation of if it only categorizes a specific use of templates or all usages of templates.
My question is if all usages of templates in C++ is categorized as metaprogramming. An explanation of why it is or isn't would also be helpful. Thank you.
My question is if all usages of templates in C++ is categorized as metaprogramming.
No.
Not all usages of templates, in C++, are metaprogramming.
Obviously it's a question of definitions but, in C++, "metaprogramming" is synonymous of "compile-time computation".
So with templates we do metaprogramming (specifically template metaprogramming) but not all uses of templates are metaprogramming.
A simple counter-example
template <typename K, typename V>
void printKeyVal (K const & k, V const & v)
{ std::cout << k << ": " << v << std::endl; }
The preceding printKeyVal() is a template function that print, to standard output (so run-time, not compile-time), a couple of generic values.
It can't run compile-time so it's "template" but isn't "metaprogramming".
More in general: std::vector is a template class that uses memory allocation. And memory allocation (till C++17; maybe in future can be different) can't be used in compile-time code.
So std::vector (contrary to std::array that, having a fixed size, doesn't use memory allocation) is a template feature that can't be used (when the use involve the instantiation of a std::vector object) for metaprogramming.
What is TMP in C++?
Template metaprogramming (TMP) in C++ is a technique for expressing and executing arbitrary algorithms in compile-time using C++ templates. It is usually enabled by the use of template specialization to emulate conditional branches and recursive template definition to emulate loops. The most well-known example is a compile-time factorial computation:
template <unsigned int n>
struct factorial {
// recursive definition to emulate a loop or a regular recursion
enum { value = n * factorial<n - 1>::value };
};
// specialization that describes "break" condition for the recursion
template <>
struct factorial<0> {
enum { value = 1 };
};
which uses both of the aforementioned techniques.
A far more common, however, is a use of TMP for type detection and transformation rather than actual numeric computation, e.g. a standard std::is_pointer utility (source):
// generic definition that emulates "false" conditional branch
template<class T>
struct is_pointer_helper : std::false_type {};
// a specialization that emulates "true" conditional branch
template<class T>
struct is_pointer_helper<T*> : std::true_type {};
template<class T>
struct is_pointer : is_pointer_helper< typename std::remove_cv<T>::type > {};
Most of the utilities provided by the standard type_traits header are implemented using TMP techniques.
Given that TMP algorithms are expressed using type definitions, it's worth mentioning that TMP is a form of declarative programming in which the logic of computation is expressed without the use of explicit control flow statements (if, else, for, etc...).
Is all usage of C++ templates a metaprogramming?
The short answer is: No. If the templates aren't used for expressing a compile-time algorithm then it's not a metaprogramming, it's a generic programming.
The primary goal for introducing templates in C++ was to enable generic programming, that is to allow reusing the same algorithms (find, copy, sort, etc...) and data structures (vector, list, map, etc...) for any types, including user-defined ones, that satisfy certain requirements.
In fact TMP in C++ was discovered by accident and was not the intended use of templates.
In summary: Template metaprogramming in C++ is the use of templates to express a compile-time algorithm, most (all?) other uses of C++ templates is a form of generic programming.
I'm trying to understand what metaprogramming is general and what it is in C++ in particular
You haven't said what you understand by metaprogramming in general yet, so your answers don't have a common starting point.
I'm going to assume the wikipedia definition is good enough for this:
Metaprogramming is a programming technique in which computer programs have the ability to treat other programs as their data.
... can be used to move computations from run-time to compile-time, to generate code using compile time computations ...
C++ doesn't generally allow self-modifying code, so I'm ignoring that. I'm also choosing not to count the preprocessor, as textual substitution at (or arguably just before) compile time is not the same as operating on the semantics of the program.
My question is if all usages of templates in C++ is categorized as metaprogramming
No, it is not.
Consider, for reference:
#define MAX(a,b) ((a) > (b) ? (a) : (b))
which is loosely the way to write a generic (type-agnostic) max function without using templates. I've already said I don't count the preprocessor as metaprogramming, but in any case it always produces identical code whenever it is used.
It simply delegates parsing that code, and worrying about types and whether a>b is defined to the compiler, in a later translation phase. Nothing here operates at compile time to produce different resulting code depending on ... anything. Nothing, at compile time, is computed.
Now, we can compare the template version:
template <typename T>
T max(T a, T b) { return a > b ? a : b; }
This does not simply perform a textual substitution. The process of instantiation is more complex, name lookup rules and overloads may be considered, and in some sense different instantiations may not be textually equivalent (eg. one may use bool ::operator< (T,T) and one bool T::operator<(T const&) or whatever).
However, the meaning of each instantiation is the same (assuming compatible definitions of operator< for different types, etc.) and nothing was computed at compile time apart from the compiler's usual (mechanical) process of resolving types and names and so on.
As an aside, it's definitely not enough that your program contains instructions for the compiler to tell it what to do, because that's what all programming is.
Now, there are marginal cases like
template <unsigned N>
struct factorial() { enum { value = N * factorial<N-1>::value }; };
which do move a computation to compile time (and in this case a non-terminating one since I can't be bothered to write the terminal case), but are arguably not metaprogramming.
Even though the Wikipedia definition mentioned moving computations to compile time, this is only a value computation - it's not making a compile-time decision about the structure or semantics of your code.
When writing C++ functions with templates, you are writing "instructions" for the compiler to tell it what to do when encountering calls of the function. In this sense, you are not directly writing code, therefore we call it meta-programming.
So yes, every C++ code involving templates is considered meta-programming
Note that only the parts defining templates functions or classes are meta-programming. Regular functions and classes are categorized as regular C++ !

C++: are "trait" and "meta-function" synonymous?

Or, does trait perhaps refer to a specific way to utilizing meta-functions?
If they are not synonymous, please point me to some examples of traits which are not meta-functions or those of meta-functions which are not traits. An actually working piece of code, perhaps within STL or Boost libraries, would be appreciated rather than a contrived toy example.
I'd like to see how experts in this field of C++ programming use these terminologies. I'm not sure if there are authoritative definitions of them...
Thanks in advance!
Clarification: It's not that I looking for any examples of traits or meta-functions. I've been using tens (if not hundreds) of them at my day job.
"Venn0110". Licensed under Public Domain via Commons.
"Meta" is C++ terminology for template programming. A clear example is the Boost Meta Programming Library (MPL).
In this sense, a meta-function is a function whose domain is not objects but C++ constructs. The common inputs are therefore types, ordinary functions and other templates.
A simple meta-function is for example template<typename T> using Foo = Bar<T, int> which has as its input a type T and as its output a type Foo<T>. Trivial, yes, but ordinary functions can be trivial too.
A trait is a metafunction whose codomain is a constant expression, often boolean. E.g. is_foo<T>::value obviously is boolean. The oldest trait in this sense is sizeof(T) whose codomain is size_t.
The terms are not equivalent.
Meta functions
The term is not defined in the C++ Standard.
There seem to be two main contending definitions:
1) "meta-functions derive/calculate/map-to values or types (based on their arguments/parameters) at compile time", and/or
constexpr functions may or may not be included in any given person's definition/conception; there's functional overlap, but much existing writing mentioning meta functions predates or just doesn't also discuss constexpr functions, so it's hard to know whether any definition or examples given deliberately leaves room for or excludes them
2) "meta-functions are functions and/or classes utilising template meta-programming", which specifically means a core of code (the template) may be reused for different parameters, performing some code generation or transformation based thereon
it's not necessarily the case that a meta-programming's final product is a compile-time type or constant - it may be a function intended for use with runtime values or data
A small survey of top google results for "meta function c++"
"metafunctions accept types and compile-time constants as parameters and return types/constants. A metafunction, contrary to its name, is a class template."
"code...executed and its result used while...compiling. ...meta-functions can compute two things: values or types"
this article uses "meta function" to refer to class templates yielding compile-time values/types
Traits
The C++ Standard doesn't define "traits", but does define "traits class"es:
17.3.25 [defns.traits] traits class
a class that encapsulates a set of types and functions necessary for class templates and function templates to manipulate objects of types for which they are instantiated
[ Note: Traits classes defined in Clauses 21, 22 and 27 are character traits, which provide the character handling support needed by the string and iostream classes. —end note ]
I'd add that traits can have values, as well as types and functions - for example rank exposes ::value. Traits provide types/values offering insight into either the parameter type itself, or the behaviour desired of the system using the trait when that system's working on variables of that type.
The Standard Library's character traits contain example of runtime functionality: for example X::length(p), X::find(p, n, c).
The <type_traits> header in the C++ Standard Library is a good place to get an initial feel for what kind of things traits can be used for.
Traits are traditionally and typically (but now C++11 provides constexpr functions not necessarily) classes, as distinct from functions meta- or otherwise.
A trait might tell you if a parameter T is constant or not, whether it's serialisable, or whether it should be compressed when transmitted via TCP: whatever some other general-purpose code might need to customise its behaviour for the range of types it handles.
Sometimes traits will be provided by the system that uses them - other times they can be supplied by client code wishing to customise its behaviour on a per-type basis.
Traits may deduce things using various tests of the parameter types, or they may be hand-crafted specialisations hard-coding values for specific types.
This ACCU introduction to traits](http://accu.org/index.php/journals/442) is worth reading, and includes this quote from the creator of C++:
Think of a trait as a small object whose main purpose is to carry information used by another object or algorithm to determine "policy" or "implementation details". - Bjarne Stroustrup
Contrasting "meta function" with "traits"
Regardless of which definition of meta function you adopt, the criteria relates to implementation details: when the function can be evaluated, and/or whether it involves code generation/transformation. That contrasts with traits, where the key concept/requirement is the purpose to which they're put, not the common - but far from universal - implementation using template specialisations or instantiations, or any capacity for compile time vs run-time evaluation.
A metafunction (in the context of C++) is a function that produces a result that can be used at compile time. The result can either be types (which can only be obtained at compile time by definition, using techniques such as partial specialisation) or values (which can be computed at compile time, using the fact that template parameters can be integral values, not just types).
A trait is essentially a class (or object) that packages up information (policy contraints, type characteristics, implementation details) for use by another class (or object) at compile time. The information packaged up can consist of type information (e.g. typedefs) or properties of interest. For example, std::numeric_limits<T> (available through <limits>) is a "type trait" which provides information about arithmetic types (e.g. is T an integral type? is the set of values a T can represent bounded or finite? is T signed? etc). The information is packaged in a form so that metaprograms (i.e. template functions or classes) can use the information at compile time. For example, a template metafunction might use information from a type trait to ensure the template is only instantiated for unsigned integral types, and trigger a compilation error if an attempt is made to instantiate the template for other types.
In this sense, a trait is a type of metafunction that provides information at compile time for use by other metafunctions.
As far as I know, there aren't any formal definitions of these terms. So I'll provide the ones that I use.
Metafunctions are templates which return a type. Usually, the returned result is a type which contains a member type named "type" so it can be accessed via the suffix ::type. Example invoking a metafunction:
using new_type = std::common_type<int, char>::type;
(see http://en.cppreference.com/w/cpp/types/common_type)
A trait would be be special type of metafunction which returns a result convertible to bool. Example:
bool ic = std::is_convertible<char, int>::type;
Note that nowadays, the requirement that a metafunction result have a member name "type" is relaxed so some metafunctions might be defined in such a manner that the ::type can be dropped. Example:
bool ic = std::experimental::is_convertible_v<char, int>;

Templates accepting "anything" in C++

I have a simple template struct associating a string with a value
template<typename T> struct Field
{
std::string name; T self;
}
I have a function that I want to accept 1-or-more Fields of any type, and the Fields may be of possible different types, so I'm using a std::initializer_list because C++, to my knowledge, lacks typed variadic arguments, cannot determine the size of variadic arguments, and must have at least one other argument to determine where to start.
The problem is that I don't know how to tell it to accept Fields that may be of different types. In Java, I would just use foo(Field<?> bar, Field<?>... baz), but C++ lacks both typed variadic arguments and wildcards. My only other idea is to make the parameter of type
std::initializer_list<Field<void*>>, but that seems like a bad solution... Is there a better way to do it?
A couple of things...
C++11 (which you seem to have since you are talking about std::initializer_list) does have typed variadic arguments, in particular they are named variadic templates
Java generics and C++ templates are completely different beasts. Java generics create a single type that stores a reference to Object and provides automatic casting in and out to the types in the interface, but the important bit is that it performs type erasure.
I would recommend that you explain the problem you want to solve and get suggestions for solutions to your problem that are idiomatic in C++. If you want to really mimic the behavior in Java (which, I cannot insist enough is a different language and has different idioms) you can use type erasure in C++ manually (i.e. use boost::any). But I have very rarely feel the need for full type erasure in a program... using a variant type (boost::variant) is a bit more common.
If your compiler has support for variadic templates (not all compilers do), you can always play with that, but stashing the fields for later in a vector may be a bit complicated for a fully generic approach unless you use type erasure. (Again, what is the problem to solve? There might be simpler solutions...)
Java generics are closer to just stuffing a boost::any into the self variable than to C++ templates. Give that a try. C++ templates create types that have no runtime or dynamic relarionship to each other by default.
You can introduce such a relationship manually, say via a common parent and type erasure and judicious use of pImpl and smart pointers.
C type variardic arguments are out of style in C++11. Variardic template arguments are very type safe, so long as your compiler has support for them (Nov 2012 CTP for MSVC 2012 has support for them (not update 1, the CTP), as does clang, and non-ancient versions of gcc).
Templates in C++ is a kind of metaprogramming, closer to writing a program that writes a program than it is to Java Generics. A Java Generic has one shared "binary" implementation, while each instance of a C++ template is a completely different "program" (which, via procedures like COMDAT folding, can be reduced to one binary implementation), whose details are described by the template code.
template<typename T>
struct Field {
T data;
};
is a little program that says "here is how to create Field types". When you pass in an int and double, the compiler does something roughly like this:
struct Field__int__ {
int data;
};
struct Field__double__ {
double data;
};
and you wouldn't expect these two types to be convertible between.
Java generics, on the other hand, create something like this:
struct Field {
boost::any __data__;
template<typename T>
T __get_data() {
__data__.get<T>();
}
template<typename T>
void __set_data(T& t) {
__data__.set(t);
}
property data; // reading uses __get_data(), writing uses __set_data()
};
where boost::any is a container that can hold an instance of any type, and access to the data field redirects through those accessors.
C++ provides means to write something equivalent to Java generics using template metaprogramming. To write something like C++ templates in Java, you'd have to have your Java program output custom Java byte or source code, then run that code in a way that allows a debugger to connect back to the code that writes the code as the source of the bugs.
There is no need to use wildcards in C++ templates, since in C++ it always knows the type, and is not "erased" like in Java. To write void foo(Field<?> bar, Field<?>... baz) method(or function) in C++, you would write:
template<class T, class... Ts>
void foo(Field<T> bar, Field<Ts>... baz);
Each Field<Ts> can be a different type. To use the variadic parameters inside the function, you just use baz.... So say you want to call another function:
template<class T, class... Ts>
void foo(Field<T> bar, Field<Ts>... baz)
{
foo2(baz...);
}
You can also expand the type with Field<Ts>..., so if you want to put it in a tuple(you can't put them in array since they can be different types):
template<class T, class... Ts>
void foo(Field<T> bar, Field<Ts>... baz)
{
std::tuple<Field<Ts>...> data(baz...);
}
This is not very idiomatic for C++. It can be done, perhaps; Coplien's book might have some ideas. But C++ is strongly typed because it believes in typing; trying to turn it into Smalltalk or fold it like a pheasant may lead to tears.

Does C++11 support types recursion in templates?

I want to explain the question in detail. In many languages with strong type systems (like Felix, Ocaml, Haskell) you can define a polymorphic list by composing type constructors. Here's the Felix definition:
typedef list[T] = 1 + T * list[T];
typedef list[T] = (1 + T * self) as self;
In Ocaml:
type 'a list = Empty | Cons ('a, 'a list)
In C, this is recursive but neither polymorphic nor compositional:
struct int_list { int elt; struct int_list *next; };
In C++ it would be done like this, if C++ supported type recursion:
struct unit {};
template<typename T>
using list<T> = variant< unit, tuple<T, list<T>> >;
given a suitable definition for tuple (aka pair) and variant (but not the broken one used in Boost). Alternatively:
using list<T> = variant< unit, tuple<T, &list<T>> >;
might be acceptable given a slightly different definition of variant. It was not possible to even write this in C++ < C++11 because without template typedefs, there's no way to get polymorphism, and without a sane syntax for typedefs, there's no way to get the target type in scope. The using syntax above solves both these problems, however this does not imply recursion is permitted.
In particular please note that allowing recursion has a major impact on the ABI, i.e. on name mangling (it can't be done unless the name mangling scheme allows for representation of fixpoints).
My question: is required to work in C++11?
[Assuming the expansion doesn't result in an infinitely large struct]
Edit: just to be clear, the requirement is for general structural typing. Templates provide precisely that, for example
pair<int, double>
pair<int, pair <long, double> >
are anonymously (structurally) typed, and pair is clearly polymorphic. However recursion in C++ < C++11 cannot be stated, not even with a pointer. In C++11 you can state the recursion, albeit with a template typedef (with the new using syntax the expression on the LHS of the = sign is in scope on the RHS).
Structural (anonymous) typing with polymorphism and recursion are minimal requirements for a type system.
Any modern type system must support polynomial type functors or the type system is too clumbsy to do any kind of high level programming. The combinators required for this are usually stated by type theoreticians like:
1 | * | + | fix
where 1 is the unit type, * is tuple formation, + is variant formation, and fix is recursion. The idea is simply that:
if t is a type and u is a type then t + u and t * u are also types
In C++, struct unit{} is 1, tuple is *, variant is + and fixpoints might be obtained with the using = syntax. It's not quite anonymous typing because the fixpoint would require a template typedef.
Edit: Just an example of polymorphic type constructor in C:
T* // pointer formation
T (*)(U) // one argument function type
T[2] // array
Unfortunately in C, function values aren't compositional, and pointer formation is subject to lvalue constraint, and the syntactic rules for type composition are not themselves compositional, but here we can say:
if T is a type T* is a type
if T and U are types, T (*)(U) is a type
if T is a type T[2] is a type
so these type constuctors (combinators) can be applied recursively to get new types without having to create a new intermediate type. In C++ we can easily fix the syntactic problem:
template<typename T> using ptr<T> = T*;
template<typename T, typename U> using fun<T,U> = T (*)(U);
template<typename T> using arr2<T> = T[2];
so now you can write:
arr2<fun<double, ptr<int>>>
and the syntax is compositional, as well as the typing.
No, that is not possible. Even indirect recursion through alias templates is forbidden.
C++11, 4.5.7/3:
The type-id in an alias template declaration shall not refer to the alias template being declared. The type produced by an alias template specialization shall not directly or indirectly make use of that specialization. [ Example:
template <class T> struct A;
template <class T> using B = typename A<T>::U;
template <class T> struct A {
typedef B<T> U;
};
B<short> b; // error: instantiation of B<short> uses own type via A<short>::U
— end example ]
If you want this, stick to your Felix, Ocaml, or Haskell. You will easily realize that very few (none?) sucessful languages have type systems as rich as those three. And in my opinion, if all languages were the same, learning new ones wouldn't be worth it.
template<typename T>
using list<T> = variant< unit, tuple<T, list<T>> >;
In C++ doesn't work because an alias template doesn't define a new type. It's purely an alias, a synonym, and it is equivalent to its substitution. This is a feature, btw.
That alias template is equivalent to the following piece of Haskell:
type List a = Either () (a, List a)
GHCi rejects this because "[cycles] in type synonym declarations" are not allowed. I'm not sure if this is outright banned in C++, or if it is allowed but causes infinite recursion when substituted. Either way, it doesn't work.
The way to define new types in C++ is with the struct, class, union, and enum keywords. If you want something like the following Haskell (I insist on Haskell examples, because I don't know the other two languages), then you need to use those keywords.
newtype List a = List (Either () (a, List a))
I think you may need to review your type theory, as several of your assertions are incorrect.
Let's address your main question (and backhanded point) - as others have pointed out type recursion of the type you requested is not allowed. This does not mean that c++ does not support type recursion. It supports it perfectly well. The type recursion you requested is type name recursion, which is a syntactic flair that actually has no consequence on the actual type system.
C++ allows tuple membership recursion by proxy. For instance, c++ allows
class A
{
A * oneOfMe_;
};
That is type recursion that has real consequences. (And obviously no language can do this without internal proxy representation because size is infinitely recursive otherwise).
Also C++ allows translationtime polymorphism, which allow for the creation of objects that act like any type you may create using name recursion. The name recursion is only used to unload types to members or provide translationtime behavior assignments in the type system. Type tags, type traits, etc. are well known c++ idioms for this.
To prove that type name recursion does not add functionality to a type system, it only needs to be pointed out that c++'s type system allows a fully Turing Complete type calculation, using metaprogramming on compiletime constants (and typelists of them), through simple mapping of names to constants. This means there is a function MakeItC++:YourIdeaOfPrettyName->TypeParametrisedByTypelistOfInts that makes any Turing computible typesystem you want.
As you know, being a student of type theory, variants are dual to tuple products. In the type category, any property of variants has a dual property of tuple products with arrows reversed. If you work consistently with the duality, you do not get properties with "new capabilities" (in terms of type calculations). So on the level of type calculations, you obviously don't need variants. (This should also be obvious from the Turing Completeness.)
However, in terms of runtime behavior in an imperative language, you do get different behavior. And it is bad behavior. Whereas products restrict semantics, variants relax semantics. You should never want this, as it provably destroys code correctness. The history of statically typed programming languages has been moving towards greater and greater expression of the semantics in the type system, with the goal that the compiler should be able to understand when the program does not mean what you want it to. The goal has been to turn the compiler into the program verification system.
For instance, with type units, you can express that a particular value isn't just an int but is actually an acceleration measured in meters per square seconds. Assigning a value that is a velocity expressed in feet per hour divided by a timespan of minutes shouldn't just divide the two values - it should note that a conversion is necessary (and either perform it or fail compilation or... do the right thing). Assinging a force should fail compilation. Doing these kinds of checks on program meaning could have given us potentially more martian exploration, for instance.
Variants are the opposite direction. Sure, "if you code correctly, they work correctly", but that's not the point with code verification. They provably add code loci where a different engineer, unfamiliar with current type usage, can introduce the incorrect semantic assumption without translation failure. And, there is always a code transformation that changes an imperative code section from one that uses Variants unsafely to one that use semantically validated non-variant types, so their use is also "always suboptimal".
The majority of runtime uses for variants are typically those that are better encapsulated in runtime polymorphism. Runtime polymorphism has a statically verified semantics that may have associated runtime invariant checking and unlike variants (where the sum type is universally declared in one code locus) actually supports the Open-Closed principle. By needing to declare a variant in one location, you must change that location everytime you add a new functional type to the sum. This means that code never closes to change, and therefore may have bugs introduced. Runtime polymorphism, though, allows new behaviors to be added in separate code loci from the other behaviors.
(And besides, most real language type systems are not distributive anyway. (a, b | c) =/= (a, b) | (a, c) so what is the point here?)
I would be careful making blanket statements about what makes a type system good without getting some experience in the field, particularly if your point is to be provocative and political and enact change. I do not see anything in your post that actually points to healthy changes for any computer language. I do not see features, safety, or any of the other actual real-world concerns being addressed. I totally get the love of type theory. I think every computer scientist should know Cateogry Theory and the denotational semantics of programming languages (domain theory, cartesian categories, all the good stuff). I think if more people understood the Curry-Howard isomorphism as an ontological manifesto, constructivist logics would get more respect.
But none of that provides reasons to attack the c++ type system. There are legitimate attacks for nearly every language - type name recursion and variant availability are not them.
EDIT: Since my point about Turing completeness does not seem to be understood, nor my comment about the c++ way of using type tags and traits to offload type calculations, maybe an example is in order.
Now the OP claims to want this in a usage case for lists, which my earlier point on the layout easily handles. Better, just use std::list. But from other comments and elsewhere, I think they really want this to work on the Felix->C++ translation.
So, what I think the OP thinks they want is something like
template <typename Type>
class SomeClass
{
// ...
};
and then be able to build a type
SomeClass< /*insert the SomeClass<...> type created here*/ >
I've mentioned this is just a naming convention wanted. Nobody wants typenames - they are transients of the translation process. What is actually wanted is what you will do with Type later on in the structural composition of the type. It will be used in typename calculations to produce member data and method signatures.
So, what can be done in c++ is
struct SelfTag {};
Then, when you want to refer to self, just put this type tag there.
When it's meaningful to do the type calculation, you have a template specialisation on SelfTag that will substitute SomeClass<SelfTag> instead of substituting SelfTag in the appropriate place of the type calculation.
My point here is that the c++ type system is Turing Complete - and that means a lot more than what I think the OP is reading everytime I've written that. Any type calculation may be done (given constraints of compiler recursion) and that really does mean that if you have a problem in one type system in a completely different language, you can find a translation here. I hope this makes things even clearer about my point. Coming back and saying "well you still can't do XYZ in the type system" would be clearly missing the point.
C++ does have the "curiously recurring template pattern", or CRTP. It's not specific to C++11, however. It means you can do the following (shamelessly copied from Wikipedia):
template <typename T>
struct base
{
// ...
};
struct derived : base<derived>
{
// ...
};
#jpalcek answered my question. However, my actual problem (as hinted at in the examples) can be solved without recursive aliases like this:
// core combinators
struct unit;
struct point;
template<class T,class U> struct fix;
template<class T, class U> struct tup2;
template<class T, class U> struct var2;
template <> struct
fix<
point,
var2<unit, tup2<int,point> >
>
{
// definition goes here
};
using the fix and point types to represent recursion. I happen not to require any of the templates to be defined, I only need to define the specialisations. What I needed was a name that would be the same in two distinct translation units for external linkage: the name had to be a function of the type structure.
#Ex0du5 prompted thinking about this. The actual solution is also related to a correspondence from Gabriel des Rois many years ago. I want to thank everyone that contributed.