And, for your examples, my_template is declared as template<class T> class my_template; ?
yes, it is
If so, instead of messing with these gymnastics of values hiding as types[...]
This seems to be much more straightforward.[...] Why? The major challenge is
user-defined operator==, but once you prohibit that, I can't see a problem.
sure, but at the cost of a loss of generality and flexibility ( see below ... )
Types and values are fundamentally different things, I'd say.
maybe, but c++ already essentially defines a one-to-one correpondence between constexpr values and types:
int i --> integral_constant<int,i>
value expression t of type T --> some_template<T,t>, whenever t is allowed as a non-type param
...
when this bijection is semantical ( eg. int <-> integral_constant ) constant values and types encoding them
essentially becomes syntactical variants, that is being essentially the same thing. This proposal renders this
bijection manifest at the syntax level.
Why is that useful?
again, see below for further examples. In general terms, whenever you write a class template taking some kind of
"value parameter", you're faced with the design problem of deciding if 1) it should be type or non-type and
2) if non-type, which one ( int,float,etc... ) ?
sure, this decision has little impact on the way the class template is defined ( at worst, one can choose to forward
everything to a internal class template taking only type parameters ).
But, it has an impact on the way the class template is used.
For example, what if you want to support both a type and a non-type ? what if you want to support non-type of different
types ? ( = you need to expose two or more variants of the same class template, provided it's possible at all )
what if you want to match ( say, while specializing or overloading ) a class template A<T,...> taking one or more arguments
unregarding of whether they are types or non-types ? ( note that there could be undocumented default template arguments, as in the STL )
you say that non-type parameters are fundamentally different from type parameters, I'd argue that 99% of the times this distinction is artificial, in the sense that one could swap them without changing the meaning of the class template, just the way users instantiate them would change ( the only exception being things like integral_constant themselves, of course ).
this proposal would allow library writers to always use ( and assume other libraries use ) types, by exploiting a semantical bijection that often already exists.
Moreover, expressions allowed for constexpr values are different from those allowed for types, this opens the way for more EDSL at the instantiation site, with no impact on the way the class template is defined ( again, see below ).
>> You've shown a conversion from "5" to std::integral_constant<int,5>. How are
>> you going to call a member function of "5" or refer to a member type of such?
>> [...] In general, no, because that won't work for scalar (built-in) types.
primitive types would have "canonical" operator typename implementation, in the same way other operators have
( user defined operator= must be a member, nonetheless, primitive types can be assigned, can't they ? ).
also, I didn't mentioned another reason why user-defined operator typename should be members: the correspondence
constexpr value<->type must be 1) a property of the value type and 2) depends on the notion of identity of the value type, in other words, it must be encapsualted in the same operator= ( and to a less extent operator== ) is.
Finally, here is some off-the-top-of-my-head examples of what you can do with this ( and you could't even with POD non-type parameters ):
1) given a "metastring" literal, pass strings of arbitrary length to templates: t<"hallo"ms>; among other things, this would be useful for ( still hypothetical ) compile reflection or the like. Of course, this would work with any literal type. AFAIK, currently, you need to wrap the thing with decltype or a macro to accomplish the same goal.
2) suppose you have a class template taking "numerical" parameters, say, a simple_histogram<T,bin1,bin2,...> class used to compute a stack-allocated histogram with compile time specified "bins" counting T's. Now, I want the bin specification to support any ( eventually mixed ) strictly weakly ordered compile time numeric ( for example, when bins ( and T ) have integer types it would give exactness guarantees, whereas for floating not ). Users would just write simple_histogram<int,1,2,3> or simple_histogram<int,1,2,3.5> or simple_histogram<mytype,myvalue,....>, and the library author would just declare a single class template <typename T, typename... Bins> simple_histogram; moreover, note that the requirements of the bins could be concept-lite-checked int this case.
3) speaking of EDSL's, one could take a different approach to expression templates:
constexpr label<1> A;
constexpr label<2> B;
evaluator< A * B * ( A^-1 ) - 2 * B > some_sensible_name; // any natural notation algebraic expression
auto a = some_specific_algebra_element{ ... };
auto b = some_other_specific_algebra_element{ ... };
some_sensible_name(a,b) // if legal, compute the expression with the best possible algorithm given a,b types
as of now, the best you can do is some variant of "evaluator<decltype( A * B * ( A^s<-1>() ) - s<2>() * B ) >", losing natural notation ...
4) as another (really-mini-)EDSL example, consider a compile time graph class template:
template< typename V, typename W >
edge;
template< typename... T >
graph;
where T can be any type ( representing a vertex ) or an edge<V,W> ( with V,W being verteces );
now, given a "vertex label" literal ""v representing some vertex label (say, an integer), with little effort we could write
graph< 1v, 2v, 3v, 1v - 2v, 3v - 1v >
instead of
graph<vertex<1>,vertex<2>,vertex<3>,edge<vertex<1>,vertex<2>>,edge<vertex<3>,vertex<1>>>
the template parameters are still of "type" type, so we can also write, say
graph<T,V,W,edge<T,V>,edge<W,V>>
or even invent a metafunction transparently working on any labeled graph:
labeled_graph<
graph< 1v, 2v, 3v, 1v - 2v, 3v - 1v >, // act as a skeleton to ease the specification of the graph topology
label<1v,T>,
label<2v,V>,
label<3v,W>
>
to some extent, this has also the effect of decoupling the instantation syntax from the class template.
ah, sorry for the lengthy post :)