Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What are the expression syntax over types C++ support?

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.

like image 217
abir Avatar asked Jun 17 '13 07:06

abir


People also ask

What is expression syntax in C?

An expression in C is a combination of operands and operators – it computes a single value stored in a variable. The operator denotes the action or operation to be performed. The operands are the items to which we apply the operation.

What is expression syntax error in Turbo C++?

Syntax errors: Errors that occur when you violate the rules of writing C/C++ syntax are known as syntax errors. This compiler error indicates something that must be fixed before the code can be compiled. All these errors are detected by compiler and thus are known as compile-time errors.

What is a discarded value expression?

Discarded-value expressions A discarded-value expression is an expression that is used for its side-effects only. The value calculated from such expression is discarded.

Does C support reference?

No, it doesn't. It has pointers, but they're not quite the same thing. For more details about the differences between pointers and references, see this SO question.


2 Answers

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)

like image 120
14 revs, 2 users 96% Avatar answered Sep 30 '22 17:09

14 revs, 2 users 96%


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();
}
like image 21
sehe Avatar answered Sep 30 '22 17:09

sehe