operator typename

281 views
Skip to first unread message

MJanes

unread,
Sep 22, 2013, 9:12:42 AM9/22/13
to std-pr...@isocpp.org
the aim is to "implicitly convert" an object of literal type into a
type encoding some compile-time state. Consider,

// a class template exposing the integer N, ala integral_constant,
// or any other non-type template argument
template<unsigned long long N> struct C{ ... };

// a literal type
struct B
{
   
// the implicitly constexpr, implicitly deleted ( in the sense
   
//  that no body is allowed ) typename "conversion" function
   
operator typename() -> C< this->N >;

   
// the state
   
unsigned long long N;
};

now, the typename operator would be "invoked" whenever a constexpr
object appears where a type-id would be expected, or when a
functional ( typename(b) ) notation is used. Moreover, the compiler
would provide typename conversions for foundamental types ( at least,
those allowed as non-type template arguments ), eg typename(5) == std:
:integral_constant<int,5>, etc...

For example:

// given some class template:
template<class T> struct A{ ... };

// given a user defined literal, implemented the obvious way
constexpr B operator"" _b( unsigned long long n );

A
<1> a1; // A<std::integral_constant<int,1>
A
<2_b> a2; // A<C<2>>

constexpr B b{4};
typename(b) c; // a C<4>

// etc...

if I'm not missing something ( and I probably do, note this is just
at the brainstorming level ... ), this would allow us

1) generalize non-type template parameters

2) ... without the usual non-type t.p. caveats ( eg template template
    params ... ), no more non-types in library interfaces

3) unlimited placeholders ( 1_,2_,... ), or the like

4) no more syntactical variants to support types and non-types ( eg
    some_templ<integral_constant<int,N>> VS some_templ_<N> )

5) generically associate type-data to object instances ( even a non-
    literal type could have a "compile time value", in some sensible
    meaning, couldn't it ? )

thoughts ?

inkwizyt...@gmail.com

unread,
Sep 22, 2013, 10:43:27 AM9/22/13
to std-pr...@isocpp.org
This will never work because you cant modify or add new types on runtime, and you code do it (e.g. `C<this->N>`, even if you try it in constexpr).
Only thing that C++ and is similar to this, is: `C<some_type::constexpr_static_object.N>`.

Unlimited placeholders are already possible in C++11:
template<char... I>
constexpr PH<I...> operator"" _ph () { return {}; }

MJanes

unread,
Sep 22, 2013, 11:24:34 AM9/22/13
to std-pr...@isocpp.org, inkwizyt...@gmail.com

This will never work because you cant modify or add new types on runtime, and you code do it (e.g. `C<this->N>`, even if you try it in constexpr).

actually, my code does nothing being not valid c++ ( obviously, special rules about the appeareance of 'this' should apply in order for such code to have a meaning ) and no, no type would be "created" at runtime; it should be the same situation as in the following (as far as I can tell, legal) code:

template< int N > struct A {};
struct B { int N; constexpr auto f() -> int  { return this->N; } };

constexpr B b{1};
A
<b.f()> a;

the typename operator would do something essentially similar ... unless there are other technical reasons why the syntax above could not be parsed/interpreted correctly, of course

Unlimited placeholders are already possible in C++11:

clearly, I was not speaking of object placeholders, but type placeholders ( as those in boost::mpl ) ...

inkwizyt...@gmail.com

unread,
Sep 22, 2013, 11:45:20 AM9/22/13
to std-pr...@isocpp.org, inkwizyt...@gmail.com
But `this->N` change `B` not `A`. Templates can use result of constexpr but contexpr cant modify templates in that way because it have same limitation as normal functions.

I dont get this type palceholders, what will be difference to:
template<int I>
struct PH {};

using AA = A<PH<0>, FOO, PH<2>>;
using BB = templ_replace<AA, PH<0>, BAR>; //equal A<BAR, FOO, PH<2>>

MJanes

unread,
Sep 22, 2013, 12:07:56 PM9/22/13
to std-pr...@isocpp.org, inkwizyt...@gmail.com


Il giorno domenica 22 settembre 2013 17:45:20 UTC+2, inkwizyt...@gmail.com ha scritto:
But `this->N` change `B` not `A`. Templates can use result of constexpr but contexpr cant modify templates in that way because it have same limitation as normal functions.


again, clearly such a typename operator would not be a normal function, it would not be a function at all. It resembles a conversion function due to the obvious analogy, but it's just a shorthand syntax for, say

template< typename T > struct A {};

struct B
{
   
int N;

   
template< int M >
   
using typename_type = std::integral_constant<int,M>;

   
constexpr int typename_func() { return this->N; }
};

constexpr B b{1};
A
< B::typename_type< b.typename_func() > > a; // operator typename would allow writing just A<b>

again, I'm not sure at all if this is technically possible or not, but still, your objections do not apply.
 
I dont get this type palceholders, what will be difference to:

not much, just a matter of esthetics :) ( you could write A<0_,1_> instead of the more verbose A<PH<0>,...> ). yes, this is a minor point actually

MJanes

unread,
Sep 23, 2013, 3:54:13 AM9/23/13
to std-pr...@isocpp.org, inkwizyt...@gmail.com
well, if the automatic interpretation as depicted above is hard or techincally unviable, here is a possibly better ( more natural, much more general ) alternative:

the operator typename would be declared as a usual member function template, the only difference being that either a trailing return type or no return type should be specified, that its unique argument should not have a name, and that the function is implicitly constexpr.

so, given

template< t-param-list >
operator typename( type-id[1] ) -> type-id[2] { function-body }

such declaration would be interpreted as if replaced by the two following hypothetical declarations:

// any attr-spec, decl-spec, cv, ref, etc.. in the original declaration belongs to this function
template< t-param-list >
type
-id[2] typename_func__() { function-body }

template< t-param-list >
using typename_type__ = type-id[1];

if no return type has been specified, std:::tuple would be assumed, with elements being the types appearing in the t-param-list.

now, let T be the type to which these members belong and t an instance of T, then 't.operator typename' would name the function typename_func__ and 'T::operator typename<t-arg-list>' would name the template alias typename_type__.

Then, for each object t of type T appearing where a type-id is expected ( or for any explicit typename(t)/typename(T) ) the following replacement occurs

...A<t>...

// --->

decl
-spec type-id[2] r{ t.operator typename() }; // in the first enclosing viable scope

...A< T::operator typename< get<0>(r), get<1>(r), ..., get< tuple_size<decltype(r)>::value >(r) > >...

finally, as a concrete example ( recall the OP for the complete use cases ):

#include <tuple>

template<class T> struct A;
template<int N> struct C;

struct B
{
   
template< int N >
   
operator typename( C<N> ) { return std::make_tuple( this->n ); }

   
int n;
};

int main()
{
   
constexpr B b{4};

    A
<b> a;
}

// as if

int main()
{
   
constexpr B b{4};

   
constexpr std::tuple<int> r{ b.operator typename() };
    A
< B::operator typename< get<0>(r) > > a; // that is, A<C<4>>
}

note that this is a much more general solution, as it allows "nesting" these generalized-non-type template parameters in operator typename delcations ...

MJanes

unread,
Sep 23, 2013, 4:32:26 AM9/23/13
to std-pr...@isocpp.org, inkwizyt...@gmail.com
corrigendum:


if no return type has been specified, std:::tuple would be assumed, with elements being the types appearing in the t-param-list.

introduces unnecessarely restrictive constraints; instead, it should be

if no return type has been specified, it's deduced as for usual auto return type deduction rules.

MJanes

unread,
Sep 23, 2013, 5:06:16 AM9/23/13
to std-pr...@isocpp.org, inkwizyt...@gmail.com
oh sorry, another error ( we can't edit post, can we ? ):  in


Il giorno lunedì 23 settembre 2013 09:54:13 UTC+2, MJanes ha scritto:
// any attr-spec, decl-spec, cv, ref, etc.. in the original declaration belongs to this function
template< t-param-list >
type
-id[2] typename_func__() { function-body }

clearly, there should be no "template< t-param-list >"  there. typename_func is a non-template function ( yes, any dependent expression appearing in type-id[2] or in the function body would give a compiler error ).
Message has been deleted

MJanes

unread,
Sep 23, 2013, 10:54:14 AM9/23/13
to std-pr...@isocpp.org, inkwizyt...@gmail.com
one last thing ... :)

firstly, thinking about it, the syntax above can be improved because using the function argument to express the "converted" typeid may be inappropriate and confusing after all. The idea remains the same, that is to compactly declare a template alias and a non-template function to be used by the implicit "type-conversion" mechanism to obtain generalized non type template parameters.
Maybe, a clearer syntax could be

template< t-param-list >
attr-spec decl-spec operator typename = typeid () optional_trailing_ret_type { function-body }

now, here is a working(*)(**) macro based prototype to play with:

(*) the prototype "works" with 1-size tuples only, and supports just one level of recursion; the true code in hypothetical case of acceptance would look the same with all the appropriate macros A,F,... dropped. The meaning should be obvious, please tell me if not ...
(**) tested in gcc4.7.2, c++11 mode


#include <tuple>

// partial macro prototype:

#include <type_traits>

#define D( E, EQ, TYPEID )            \
   
using typename_alias_ = TYPEID;    \
   
constexpr auto typename_func_

#define A_operator
#define typename_A typename_alias_
#define A( E ) A_##E##_A

#define F_operator
#define typename_F typename_func_
#define F( E ) F_##E##_F

#define X( M ) X_
#define X_( M )                                                        \
    std
::remove_reference<decltype(M)>::type::A(operator typename)    \
       
< std::get<0>( (M).F(operator typename)() ) >                \


#define E( M )                                                        \
    std
::remove_reference<decltype(M)>::type::A(operator typename)    \
       
< std::get<0>( (M).F(operator typename)() ) >                \

#define RE( M )                                                        \
    std
::remove_reference<decltype(M)>::type::A(operator typename)    \
       
< E( std::get<0>( (M).F(operator typename)() ) ) >            \

// example:


template< class T > struct A {};
template< int N > struct C {};
template< class T > struct C2 {};

struct B
{
   
int n;

   
template< int N >
    D
( operator typename, =, C<N> ) () -> std::tuple<int> { return std::make_tuple( n ); }
};

struct B2
{
    B b
;

   
template< class T >
    D
( operator typename, =, C2<T> ) () -> std::tuple<B> { return std::make_tuple( b ); }
};

int main()
{
   
constexpr B b{1};
   
constexpr B2 b2{b};

    B
::A(operator typename)<0> c; // a C<0>
    b
.F(operator typename)(); // returns a tuple<int>

    X
(typename)(b) c2; // a C<1>

    A
<E(b)> a; // an A<C<1>>
    A
<RE(b2)> a2; // an A<C2<C<1>>>
}

inkwizyt...@gmail.com

unread,
Sep 23, 2013, 12:48:58 PM9/23/13
to std-pr...@isocpp.org, inkwizyt...@gmail.com
I think this proposition should be simplified to allowing POD classes as template parameters (probably with restriction of floating points fields).
You will probably get most functionality that you want without adding new syntax and rules to language.

MJanes

unread,
Sep 23, 2013, 1:06:12 PM9/23/13
to std-pr...@isocpp.org, inkwizyt...@gmail.com
Il giorno lunedì 23 settembre 2013 18:48:58 UTC+2, inkwizyt...@gmail.com ha scritto:
I think this proposition should be simplified to allowing POD classes as template parameters (probably with restriction of floating points fields).
You will probably get most functionality that you want without adding new syntax and rules to language

I defintely disagree

firstly, types usable in this proposal can have much reacher interfaces than pods ( consider optional, tuples, ... ), the only advantage of pods being the theorical possibility of using brace init syntax, but this would introduce its own problems and, IMO, would reduce readability; note that with this mechanism the typename conversion can be defined recursively, by reusing previously defined higher-level literal types.

second, in my view, the primary reason behind this proposal is to avoid the drawbacks of using non-type and type parameters in template based libraries, see the recent thread in this group ( "Variadic template template problem: the issue with mixed types/values template parameters" ) for an example.

third, all the new stuff ( maybe, excluding the implicit subsitution mechanism ) can be expressed in terms of existing entities ( a template alias and a normal member function ), so no true new "rule" would be introduced.

Jens Maurer

unread,
Oct 14, 2013, 4:54:05 PM10/14/13
to std-pr...@isocpp.org
On 09/22/2013 03:12 PM, MJanes wrote:
> the aim is to "implicitly convert" an object of literal type into a
> type encoding some compile-time state. Consider,

This seems to be some kind of syntactic sugar.

What's the equivalent usage syntax with and without that syntactic sugar?

Why is that usage sufficiently prevalent and icky to warrant a core
language extension?

(Non-type template parameters won't go away with this, I'd guess,
since "5" appears to be mapped to std::integral_constant<int, 5>.)

What's the abstract mapping provided by the "operator typename"?
Supposedly, it's constant value -> type .
Why is that a "member" of the constant value, as opposed to a
"constructor" for the type? Or even a free meta-function?

Thanks,
Jens



MJanes

unread,
Oct 15, 2013, 3:38:41 AM10/15/13
to std-pr...@isocpp.org
This seems to be some kind of syntactic sugar.

yes it is


What's the equivalent usage syntax with and without that syntactic sugar?

use a value expression where a type-id is expected. So,

instead of my_template<std::integral_constant<int,5>> you write my_template<5>,
instead of my_template<std::tuple<std::integral_constant<int,5>,mystring<'h','a','l','l','o'>>> you write my_template<make_tuple(5,"hallo"_myliteral)>,
etc...


Why is that usage sufficiently prevalent and icky to warrant a core
language extension? 
(Non-type template parameters won't go away with this, I'd guess,
since "5" appears to be mapped to std::integral_constant<int, 5>.)

the aim is twofold:

1) generalize non-type template parameters to any literal type. But, instead of actually allowing such non-type template parameters ( which seems not possible due to name mangling difficulties ) we allow turning literal objects into types during template instantiation. Yes, "usual" non-type template parameter would not be replaced, they would remain as basic building blocks for making higher level constructs, hence fading away <by usage>, in the same way pointers became hidden beneath std::vector-like interfaces.

2) uniformize type and non-type template parameters; in other words, library authors adopting such a language feature would not <use> non-type template parameters anymore, just types ( the template< int N > class mytemplate; would just take an integral_constant-like type instead; users would still instantiate the thing as before ). This has the advantage of make template code less redundant ( say, see boost MPL and the int_, integral_constant variants ), more general and flexible ( a template template parameter V<T,W> would match <essentially any> binary class template ) and simpler TMP ( no need to consider two kind of template params, or to forcibly limit a library facility to one of them ).


What's the abstract mapping provided by the "operator typename"?
Supposedly, it's  constant value -> type  .

yes, it is. Recapitulating, the machinery works in two phases; the easy one ( the actual operator typename declaration ) is just a compact symtax to declare a template alias and a (constexpr) zero-ary member function

template< ... >
using optypename_alias = ...;

constexpr auto optypename_func() -> ... { ... }

the supposedly hard part, is to replace any value expression t of literal type T appearing where a type id is expected, with something like


...A<t>...

// --->

constexpr auto r{ t.optypename_func() }; // in the first enclosing viable scope

...A< T::optypename_alias< get<0>(r), get<1>(r), ..., get< tuple_size<decltype(r)>::value - 1 >(r) > >...

recursively.


Why is that a "member" of the constant value, as opposed to a
"constructor" for the type?  Or even a free meta-function?

because, conceptually and practically, the operator typename ( or whatever syntax one chooses ) is a pair of members: a template alias and a member function.


ah, thank you for the reply !

Jens Maurer

unread,
Oct 16, 2013, 4:57:31 PM10/16/13
to std-pr...@isocpp.org
On 10/15/2013 09:38 AM, MJanes wrote:
> What's the equivalent usage syntax with and without that syntactic sugar?
>
>
> use a value expression where a type-id is expected. So,
>
> instead of my_template<std::integral_constant<int,5>> you write my_template<5>,
> instead of my_template<std::tuple<std::integral_constant<int,5>,mystring<'h','a','l','l','o'>>> you write my_template<make_tuple(5,"hallo"_myliteral)>,
> etc...

And, for your examples, my_template is declared as
template<class T>
class my_template;

?


If so, instead of messing with these gymnastics of values hiding as types,
shouldn't we just allow trivial types without operator== for non-type template
parameters?

struct Data
{
int x;
char c[5];
};

template<Data value>
class my_template;

my_template<Data{ 5, 'h', 'a', 'l', 'l', 'o' }>


This seems to be much more straightforward.

> Why is that usage sufficiently prevalent and icky to warrant a core
> language extension?
>
> (Non-type template parameters won't go away with this, I'd guess,
> since "5" appears to be mapped to std::integral_constant<int, 5>.)
>
>
> the aim is twofold:
>
> 1) generalize non-type template parameters to any literal type. But,
> instead of actually allowing such non-type template parameters (
> which seems not possible due to name mangling difficulties )

Why? The major challenge is user-defined operator==, but once
you prohibit that, I can't see a problem.

> we allow
> turning literal objects into types during template instantiation.
> Yes, "usual" non-type template parameter would not be replaced, they
> would remain as basic building blocks for making higher level
> constructs, hence fading away <by usage>, in the same way pointers
> became hidden beneath std::vector-like interfaces.


> 2) uniformize type and non-type template parameters; in other words,
> library authors adopting such a language feature would not <use>
> non-type template parameters anymore, just types ( the template< int
> N > class mytemplate; would just take an integral_constant-like type
> instead; users would still instantiate the thing as before ).

Why is that useful? Types and values are fundamentally
different things, I'd say.

> This
> has the advantage of make template code less redundant ( say, see
> boost MPL and the int_, integral_constant variants ), more general
> and flexible ( a template template parameter V<T,W> would match
> <essentially any> binary class template ) and simpler TMP ( no need
> to consider two kind of template params, or to forcibly limit a
> library facility to one of them ).

I'd prefer to attack any (perceived or real) issues with writing
metaprogramming libraries directly, rather than forcing clients
of such libraries to re-write their templates in a novel way.

> What's the abstract mapping provided by the "operator typename"?
> Supposedly, it's constant value -> type .
>
>
> yes, it is. Recapitulating, the machinery works in two phases; the
> easy one ( the actual operator typename declaration ) is just a
> compact symtax to declare a template alias and a (constexpr) zero-ary
> member function
>
> template< ... >
> using optypename_alias = ...;
>
> constexpr auto optypename_func() -> ... { ... }
>
> the supposedly hard part, is to replace any value expression t of
> literal type T appearing where a type id is expected, with something
> like

>
> ...A<t>...
>
> // --->
>
> constexpr auto r{ t.optypename_func() }; // in the first enclosing viable scope

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?

> ...A< T::optypename_alias< get<0>(r), get<1>(r), ..., get< tuple_size<decltype(r)>::value - 1 >(r) > >...
>
> recursively.
>
> Why is that a "member" of the constant value, as opposed to a
> "constructor" for the type? Or even a free meta-function?
>
>
> because, conceptually and practically, the operator typename ( or
> whatever syntax one chooses ) is a pair of members: a template alias
> and a member function.

In general, no, because that won't work for scalar (built-in) types.

Jens

MJanes

unread,
Oct 17, 2013, 4:25:16 AM10/17/13
to std-pr...@isocpp.org
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 :)
Reply all
Reply to author
Forward
0 new messages