3

C++

I want to know if a pointer that isn’t already a two-const pointer (e.g. T const * const) can be implicitly or explicitly cast, processed through something (e.g. a function), or otherwise converted, to yield a T const * const, without or before being used to initialize a variable that is declared as a T const * const. How can I do this?

I thought that if I started with a T*, then one const_cast (or two, in case the cast can only cast in one const at a time) would suffice, but apparently it doesn’t. Many variables in the code show a different unsuccessful attempt at yielding T const * const through casting or returning from a function. Every cast failed to return a pointer with a trailing const. (I refer to the const to the left of * as the leading const and the one to the right of * as the trailing const.) Because of the unsuccessful casts, I tried unsuccessfully to force the const through direct initialization. This was compiled in VC11. g++ on stack-crooked.com gives a logically equivalent console output, albeit with different names for typeid(/*...*/).name().

#include <iostream>
#include <typeinfo>
using namespace std;

int const * const foo()
{
    return nullptr;
}

int main()
{
    int x = 7;

    auto a1 = &x;
    cout << typeid(a1).name() << endl;

    auto a2 = const_cast<int const *>(&x);
    cout << typeid(a2).name() << endl;

    auto a3 = const_cast<int * const>(&x);
    cout << typeid(a3).name() << endl;

    auto a4 = const_cast<int const * const>(&x);
    cout << typeid(a4).name() << endl;

    auto a5 = const_cast<int const * const>(a4);
    cout << typeid(a5).name() << endl;

    auto a6 = (int const * const) &x;
    cout << typeid(a6).name() << endl;

    auto a7 = static_cast<int const * const>(a4);
    cout << typeid(a7).name() << endl;

    auto a8 = reinterpret_cast<int const * const>(a4);
    cout << typeid(a8).name() << endl;

    auto a9 = foo();
    cout << typeid(a9).name() << endl;

    int const * const a10 = &x;
    cout << typeid(a10).name() << endl;
    cout << ( typeid(a10) == typeid(a4) ) << endl;

    auto a12 = a10;
    cout << typeid(a12).name() << endl;
    cout << ( typeid(a12) == typeid(a4) ) << endl;
}

Expected results vs. actual results, and questions:

Question numbers correspond to the same-numbered a# variables.

  1. Got expected result int *
  2. Got expected result int const *
  3. Expected int* const, but got int*. Did the const_cast ignore its trailing const argument and why? Since the return is the same constness and type as the argument, did the cast run at all?
  4. Expected int const * const, but got int const*. Did the const_cast ignore its trailing const argument and why?
  5. I wanted to see if const_cast<int const * const> will include a trailing const in the result given that the argument a4 already has a leading const. Expected int const * const. Got int const*. Did the const_cast ignore its trailing const argument and why?
  6. Expected int const * const. Got int const*. Why would the explicit cast still exclude the trailing const?
  7. Expected int const * const. Got int const*. Why would the static_cast exclude the trailing const?
  8. Expected int const * const. Got int const*. Why would the reinterpret_cast exclude the trailing const?
  9. Expected int const * const. Got int const*. Why would an initialization to the int const * const return of a function still exclude the trailing const in the result?
  10. Expected int const * const. Got int const* from the console output but not from the debugger. a10 is explicitly declared as int const * const, so why does the typeid().name() exclude the trailing const? operator== yields 1, so why is the typeid() itself (not just the name) for a10 equivalent to that of a4? The VC11 debugger lists a10’s type as int const * const. Why is it different than the one from typeid() and typeid().name()? Which one is correct?
  11. variable name a11 omitted because it looks like the word “all”.
  12. I expected a12 to be int const * const because it is initialized to a10, which was explicitly declared int const * const. operator== yields 1, so typeid() is still int const*. Got int const* from both the console output and the debugger. Why are they different from the expected result?

Are all casts, function returns, and initializations limited to only casting in one const at a time? Is the leading const the only thing they can cast in?

CodeBricks
  • 1,771
  • 3
  • 17
  • 37

1 Answers1

3

const_cast does work the way you think it does. However, auto doesn't do what you think it does. auto works similarly to function template parameter deduction (in fact, it's defined in terms of the latter). Now consider:

template<typename T>
void f(T x);

f(42);
int const n = 42;
f(n);

Both calls are to f<int>(), not f<const int>(). Top-level const modifiers are ignored by template parameter deduction. For the same reason, in this example

auto a = 42; a = 84;
auto b = n;  b = 84;

variables a and b are of type int, not const int, and can be modified.

Igor Tandetnik
  • 50,461
  • 4
  • 56
  • 85
  • Thanks for the answer; that clarifies many issues, but do you have an answer for #10? Also, do you mean the leading `const` isn’t a top-level `const` modifier; only the trailing `const` is? Since `auto` ignores top-level const modifiers, how can I observe, preferably store, the immediate results of each cast before the top-level const modifier is lost? Can `const_cast(&t_type)` cast a `T*` directly to `T const * const`, or do I need two casts, one for each `const`? – CodeBricks Oct 13 '13 at 07:28
  • 1
    **5.2.8/5** The top-level cv-qualifiers of the glvalue expression or the *type-id* that is the operand of `typeid` are always ignored. – Igor Tandetnik Oct 13 '13 at 13:41
  • 1
    The const that applies to the variable being declared is the top-level `const` modifier, whether it lexically appears at the beginning or at the end. In `const int n = 42` and `int const n = 42`, `const` modifiers are top-level. In `const int* const! p`, `int const * const! p`, `typedef const int* CP; const! CP p`, I marked the top-level qualifiers with an exclamation point. – Igor Tandetnik Oct 13 '13 at 13:46
  • Your replies have been very helpful, but do you have answers to the last two questions in my previous comment to your answer? Sorry if your answers and comments already apply. My Standardese is poor. – CodeBricks Oct 13 '13 at 19:27
  • Why do you **want** to observe top-level `const` modifier? What's the ultimate goal of the exercise? You are trying hard to make a const rvalue - but an rvalue is, by definition, not modifiable; a const modifier on it is redundant. '5' is no more or less constant than `const_cast(5)`. You can assign either to a `const int` variable. Similarly, you can happily write `const int* const p = const_cast(&x)`; – Igor Tandetnik Oct 13 '13 at 22:58
  • Goal: see what `const_cast` returns including all const qualifiers. Observation of top-level const modifier unnecessary as soon as I find out. From your answer: “const_cast does work the way you think it does.” I'd like more clarification because I don't know exactly what it returns; OP asks **if** the top-level const can be cast in and **how many** casts is needed to cast in both top- and non-top-level consts. Though OP didn’t say the pointer to cast from is an lvalue, that's what I mean.Not every cast in OP was from rvalue (e.g. `a4`).Tried to find answer from Standard but some parts dubious – CodeBricks Oct 14 '13 at 00:11
  • Dubious parts of Standard: C++11, **5.2.11/1 [expr.const.cast]** gives `const_cast(v)`, **5.2.11/3** states `T1` can be `const_cast` to `T2` if “T1 is cv1,0 pointer to cv1,1 pointer to… cv1,n T and T2 is cv2,0 pointer to cv2,1 pointer to…pointer to cv2,n T…where cv1,k and cv2,k nay be different cv-qualifications…” **/1** appears to group cv-qualifications into `T` but `/3` does not. Even if it means I can cast `T1` to `T2` including the cv-qualifications, I’m not sure if cv-qualifiers (**3.9.3**) include both the top-level const and the non-top-level const. – CodeBricks Oct 14 '13 at 00:12
  • **/1** simply explains the syntax of `const_cast` expression: you cast an expression of some type to a different type. **/3** and below specify how these type types must be related to each other, for the cast to be valid. In the phrase "cv1,0 pointer to cv1,1 pointer to… cv1,n T", cv1,0 is the top-level qualifier. – Igor Tandetnik Oct 14 '13 at 03:12