17

I was working with a templated class which takes a set of integers. The code was like,

template<unsigned... Idx>
struct work{ ... };

Then I realized, user may need to provide either a set of integers, or a range of integers. So, I changed the syntax little to support instantiation like,

work<indexes<1,3,2,4> > //instead of work<1,3,2,4>
work<span<1,4> > //same as work<1,2,3,4> 

While, in C++ we have large number of operators, and can be used to formulate exotic expression templates (say boost::xpressive, boost::lambda, boost::spirit etc) , possibilities for type manipulation is much less.

In a boostcon keynote by Sean Parent, he noted one still can not write pair<int> to denote a pair of integers. In my persinal library, I made a syntax like tuple<int[3]> to denote a tuple of 3 integers, instead of writing a tuple with 3 int in the type arguments, noting that I do not write a raw array as tuple argument anywhere! (note: std::array<int,3> is not same as the above, as std::array can not store references while tuple can, say std::tuple<int&,int&,int&> is possible)

So, I want to know what are the different kind of "type expressions" I can write?

So far I can think of object type, function type, reference type, with/without cv modifiers, pointers etc. e.g

    template<class T>
    struct tpl;

    using t1 = tpl<int>;//simple type
    //function (can have function pointer/reference also) 
    // e.g. void(*)(int,float) or void(&)(int,float)
    using t2 = tpl<void(int,float)>;
    //array can have pointer / reference also
    //e.g. int(&)[4] or int (*)[4]
    using t3 = tpl<int[4]>;
    using t4 = tpl<int[]>;
    using t5 = tpl<int const>;//with cv modifiers
    using t6 = tpl<int*>;//with pointer
    using t7 = tpl<int&>;//with reference (& or &&)
    using t8 = tpl<tpl<int> >; //template itself
    using t9 = tpl<void(...)>; //variadic functions
    using t10 = tpl<R C::*>; //pointer to member

But I believe, many more are possible.

NOTE: This question is purely theoretical, I just want to know all kinds of syntax I can write inside <> as type argument, and not about the readability/morality aspect of it, or even how can I implement some of the examples I had given, like the work class.

abir
  • 1,797
  • 14
  • 26
  • There is an infinity of answers to this question, for example the infinite series `char, char*, char**, ...`. It cannot therefore be answered in a finite way. – user207421 Jun 17 '13 at 07:33
  • 2
    Yes, yes, I know! But if they are regular, I can still express them as `a type followed by zero or more *`, or any other way, as long as it expresses the intent. – abir Jun 17 '13 at 07:40
  • 2
    Look for `type_id` in the standard. – n. m. could be an AI Jun 17 '13 at 09:21
  • @EJP A lot of infinite things can be expressed in a finite way. – Paul Manta Jun 17 '13 at 13:14
  • @abir pardon the misunderstanding, then. Perhaps the sample could have been omitted/put at the end. Also, "How much *** can we have" is probably not the best fit for an SO question – sehe Jun 17 '13 at 14:30
  • @EJP Try "Any type `T` with one or more layers of pointer indirection, e.g., `char*, char**, char***, ...`." I'd say that sentence is finite. :) – Timothy Shields Jun 17 '13 at 16:44
  • Unsure why this has 4 close votes. There's an explicit part in the standard which covers this exact question, which means WG21 considered a question worth addressing. – MSalters Jun 17 '13 at 17:00

2 Answers2

13

Compound types can be constructed using the declarator syntax - found in [dcl.decl].

Underlying this syntax are six fundamental constructs, within which any T can be substituted by any of the other constructs in the list. [In the following, (T) represents a list of zero or more types (which may end in '...'), and <T> represents a list of one or more types.]

T // T
T* // pointer to T
T& // reference to T
T[n] // array of size 'n' of T
T C::* // pointer to C::member of type T
T (T) // function taking '(T)' and returning T

EDIT: The type of a class template specialization can be subsituted for any T:

C<T> // specialization of class template C with arguments '<T>'

There are combinations of the above that produce constructs which have special significance:

T (*)(T) // pointer to function taking '(T)' and returning T
T (C::*)(T) // pointer to C::member-function taking '(T)' and returning T

Additionally, some of the constructs may be cv-qualified:

const T // const T
T* const // const pointer to T
T C::* const // const pointer to C::member of type T

Not all of the combinations result in valid types. According to [basic.compound], only the following combinations can be used:

Compound types can be constructed in the following ways:

  • arrays of objects of a given type
  • functions, which have parameters of given types and return void or references or objects of a given type
  • pointers to void or objects or functions (including static members of classes) of a given type
  • references to objects or functions of a given type
  • pointers to non-static class members, which identify members of a given type within objects of a given class

Additional restrictions are mentioned:

[dcl.ptr] there are no pointers to references

[dcl.ref] There shall be no references to references, no arrays of references, and no pointers to references

[dcl.mptr] A pointer to member shall not point to ... a member with reference type, or "cv void."

[dcl.fct] The parameter list (void) is equivalent to the empty parameter list. Except for this special case, void shall not be a parameter type. ... If the type of a parameter includes a type of the form “pointer to array of unknown bound of T” or “reference to array of unknown bound of T,” the program is ill-formed. Functions shall not have a return type of type array or function.

Some of the possible constructs cannot be used as template-arguments. When you explicitly specify a set of template-arguments, the compiler must check that the template-arguments can be substituted for the template-parameters without resulting in an 'invalid type'. According to [temp.deduct]\2, the following constructs constitute invalid types:

Type deduction may fail for the following reasons:

  • Attempting to create an array with an element type that is void, a function type, or a reference type, or attempting to create an array with a size that is zero or negative.

    template <class T> int f(T[5]);
    int I = f<int>(0);
    int j = f<void>(0); // invalid array
    
  • Attempting to use a type that is not a class type in a qualified name.

    template <class T> int f(typename T::B*);
    int i = f<int>(0);
    
  • Attempting to use a type in the qualifier portion of a qualified name that names a type when that type does not contain the specified member, or if the specified member is not a type where a type is required.

    template <class T> int f(typename T::B*);
    struct A {};
    struct C { int B; };
    int i = f<A>(0);
    int j = f<C>(0);
    
  • Attempting to create a pointer to reference type.

  • Attempting to create a reference to a reference type or a reference to void.

  • Attempting to create "pointer to member of T" when T is not a class type.

    template <class T> int f(int T::*);
    int i = f<int>(0);
    
  • Attempting to perform an invalid conversion in either a template argument expression, or an expression used in the function declaration.

    template <class T, T*> int f(int);
    int i2 = f<int,1>(0); // can’t conv 1 to int*
    
  • Attempting to create a function type in which a parameter has a type of void.

  • Attempting to create a cv-qualified function type.

EDIT: This is based on C++03, but also applies to C++11 (which adds rvalue reference types)

willj
  • 2,991
  • 12
  • 24
  • 1
    The others I know about are references like `T&` , `T&&`,`T(&)(A...)` array like `T[]` – abir Jun 17 '13 at 13:27
  • 1
    The list actually misses templates themselves. That's relevant to the question: `using t8 = tpl>;` – MSalters Jun 17 '13 at 17:07
  • You are missing varargs functions. They are very distinct from variadic template functions, and both `T foo(U..., ...)` and `T foo(...)` are different from `T foo(U...)`. – rubenvb Jun 17 '13 at 19:31
6

I'm not entirely sure what you are asking about. I thought the sample you gave was interesting and played a little bit with it.

I came up with an implementation that makes span<a,b> to be a template alias for indexes<a, ..., b>, using the trick of type deduction in constexpr functions:

template <int a, int b> using span = decltype(expand_span<a,b>());

Now you can have your cake and eat it:

////////////////////////////////////////////////////////////////
// using indirect template arguments
template<typename> struct indirect_work { };

void test_indirect()
{
    indirect_work<indexes<1,2,3,4>> x;
    indirect_work<span<1,4>>        y;

    x = y; // x and y are of identical types
    static_assert(std::is_same<indexes<1,2,3,4>, span<1,4>>::value, "fact check");
}

But, perhaps more interestingly, you can still have your primary work template take a raw <int...> template argument list:

////////////////////////////////////////////////////////////////
// using direct template arguments
template<int...> struct direct_work { };

// deduction alias:
template<int... direct> constexpr direct_work<direct...> deduction_helper(indexes<direct...>);
template <typename Idx> using deduce = decltype(deduction_helper(Idx{}));

void test_direct()
{
    direct_work<1,2,3,4> x;
    deduce<indexes<1,2,3,4>> y;
    deduce<span<1,4>> z;

    static_assert(std::is_same<decltype(x), decltype(y)>::value, "fact check");
    static_assert(std::is_same<decltype(x), decltype(z)>::value, "fact check");
}

See a complete working demonstration here: gcc on ideone. I compiled it with clang locally.


Full code

Code for expand_span duplicated here in case link should go dead:

#include <type_traits>

template <int...> struct indexes {};

namespace {
    template<int a, int... other>
        constexpr indexes<a, other...> combine(indexes<other...> deduce);

    template<int a, int b, typename Enable = void> struct expand_span_; // primary

    template<int a, int b>
    struct expand_span_<a, b, typename std::enable_if< (a==b), void >::type> {
        static constexpr indexes<a> dispatch();
    };

    template<int a, int b>
    struct expand_span_<a, b, typename std::enable_if< (a<b), void >::type> {
        static constexpr decltype(combine<a>(expand_span_<a+1, b>::dispatch())) 
            dispatch();
    };

    template<int a, int b>
    constexpr auto expand_span() -> decltype(expand_span_<a,b>::dispatch());
}

template <int a, int b> using span = decltype(expand_span<a,b>());

////////////////////////////////////////////////////////////////
// using indirect template arguments
template<typename> struct indirect_work { };

void test_indirect()
{
    indirect_work<indexes<1,2,3,4>> x;
    indirect_work<span<1,4>>        y;

    x = y; // x and y are of identical types
    static_assert(std::is_same<indexes<1,2,3,4>, span<1,4>>::value, "fact check");
}

////////////////////////////////////////////////////////////////
// using direct template arguments
template<int...> struct direct_work { };

// deduction alias:
template<int... direct> constexpr direct_work<direct...> deduction_helper(indexes<direct...>);
template <typename Idx> using deduce = decltype(deduction_helper(Idx{}));

void test_direct()
{
    direct_work<1,2,3,4> x;
    deduce<indexes<1,2,3,4>> y;
    deduce<span<1,4>> z;

    static_assert(std::is_same<decltype(x), decltype(y)>::value, "fact check");
    static_assert(std::is_same<decltype(x), decltype(z)>::value, "fact check");
}

int main()
{
    test_indirect();
    test_direct();
}
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Thanks, but I am not looking for how to implement that `work` class. If you look at the question, I am asking what all I can put inside `<>` as template type argument. We have fairly clear idea about EDSL, but how much expressive things can we write as type argument? – abir Jun 17 '13 at 12:37
  • PS. have you looked at ProtoV5 (https://github.com/ericniebler/proto-0x)? It uses function types extensively to mimic EDSLs in it's template arguments. E.g. [A First Look at Proto-0x](http://www.youtube.com/watch?v=JF6YM0XzHnE), e.g. `constexpr map_list_of_expr map_list_of {};` on [slide 37](https://github.com/boostcon/cppnow_presentations_2013) – sehe Jun 17 '13 at 14:36
  • Yes, I had noticed proto has those beautiful function types, the other intuitive syntax's are T[N] to say N times T (who uses raw array anymore!) , and T* to say zero or more T (again who uses native pointer?) . I do not know if c++14 object alias/expression alias will give something more beautiful like `tuple<3_times >` – abir Jun 17 '13 at 15:38
  • I agree `T[N]` has quite some expressive potential. (However I think using `tuple` to mean `tuple` violates the principle of least surprise in spectacular ways :)). Long story short, maybe you should take up Haskell or [Boo](http://www.manning.com/rahien/) :) (Or Factor, if concatenative languages don't deter you :)) – sehe Jun 17 '13 at 16:54
  • 1
    There's of course the precedence of `std::function` not containing a `void(int)`. – MSalters Jun 17 '13 at 17:05
  • @MSalters aherm. No kidding! You're saying there are other library classes that _also_ don't have surprising interfaces! Aha. Well, that's why you don't expect that `tuple` models a _unary calleable_ and you _do_ expect `std::add_reference` to describe an array reference type, but not `tuple` (anyways, \@abir: what's wrong with `tuple`? Cf. `array`) – sehe Jun 17 '13 at 21:04
  • @not-sehe Nothing wrong at all! But how can I say tuple AND tuple or even tuple ? For functions, I can `overload`, but for class template how can I make an argument either type parameter or value parameter? And array is different beast all together as array does not work with references. so array or array does not work. Moreover, array is homogeneous, while tuple is heterogeneous. – abir Jun 18 '13 at 05:30
  • Good point on the 'immobility' of the primary template (i.e. type/non-type congruency). The rest was pretty well known. I'm pretty confident I could expand my sample implementation to support `work, float, repeat<0, std::string> >` and do what you expect. Would that be something you want (sadly, we can't have `repeat(7, int)` without preprocessor magic. Yet.) – sehe Jun 18 '13 at 07:03
  • @not-sehe That is what I exactly do. It is nearly same as the `work` example. If we use templates they looks very much like LISP. e.g. `and_,not_ ,Args...> > ` . We can have `repeat(int_<7>, int)` also, but again prefix form. `work` is infix form, but not so clear (after all which DSL is clear? I had hard time to digest `&` as serialization operator for boost!). I was looking for all interesting things I can write in `<>` pair! C++ is funny! – abir Jun 18 '13 at 11:04