mardi 14 décembre 2021

Why template function with 'const' from left of parameter type is misbehaving against the rule of type deduction for pointer argument?

Consider this pseudo code for a type deduction case:

template<typename T> void f(ParamType param);

Call to function will be:f(expr);

According to type deduction case where ParamType is not a reference, pointer, nor a universal reference (see S. Meyers "Effective Modern C++", p.14), but passed by value, to determine type T, one needs firstly to ignore the reference and const part of 'expr' and then pattern-match exprs type to determine T.

The driver will be:

void PerformTest() {

    int i = 42;
    int* pI = &i;
    f_const_left(pI);
    f_non_template_left(pI);
    f_const_right(pI);
    f_non_template_right(pI);
}

Now consider these functions, which, using this deduction rule, are showing some counter-intuitive results while being called with pointer as an argument:

template<typename T> void f_const_left(const T t) {
    // If 'expr' is 'int *' then, according to deduction rule for value parameter (Meyers p. 14),
    // we need to get rid of '&' and 'const' in exp (if they exist) to determine T, thus T will be 'int *'.
    // Hence, ParamType will be 'const int *'.
    // From this it follows that:
    //    1. This function is equivalent to function 'func(const int * t){}'
    //    2. If ParamType is 'const int *' then we have non-const pointer to a const object,
    //       which means that we can change what pointer points to but cant change the value
    //       of pointer address using operator '*'
    *t = 123;// compiler shows no error which is contradiction to ParamType being 'const int *'

    t = nullptr; // compiler shows error that we cant assign to a variable that is const

    // As we see, consequence 2. is not satisfied: 
    // T is straight opposite: instead of being 'const int *'
    // T is 'int const *'.
    // So, the question is:
    // Why T is not 'const int*' if template function is f(const T t) for expr 'int *' ?
}

Consider consequence 1.:

Lets create an equivalent non-template function:

void f_non_template_left(const int* t) {
    // 1. Can we change the value through pointer?
    *t = 123; // ERROR: expression must be a modifiable lvalue
    // 2. Can we change what pointers points to?
    t = nullptr; // NO ERROR

    // As we can see, with non-template function situation is quite opposite.
}

For for completeness of the experiment, lets also consider another pair of functions but with 'const' being placed from the right side of a T: one template function and its non-template equivalent:

template<typename T> void f_const_right(T const t) {
    // For expr being 'int *' T will be 'int *' and ParamType will be 'int * const',
    // which is definition of a constant pointer, which cant point to another address,
    // but can be used to change value through '*' operator.
    // Lets check it:

    // Cant point to another address:
    t = nullptr; // compiler shows error that we cant assign to a variable that is const

    // Can be used to change its value:
    *t = 123;
    // So, as we see, in case of 'T const t' we get 'int * const' which is constant pointer, which
    // is intuitive.
}

Finally, the non-template function with 'const' from the right side of type:

void f_non_template_right(int* const t) {
    // 1. Can we change the value through pointer?
    *t = 123; // No errors
    // 2. Can we change what pointers points to?
    t = nullptr; // ERROR: you cant assign to a variable that is const

    // As we can see, this non-template function is equivalent to its template prototype
}

Can someone explain why there is such insonsistency between template and non-template functions ? And why template function with 'const' on the left is behaving not according to the rule of deduction?

Aucun commentaire:

Enregistrer un commentaire