How does this overloading of square brackets work

255 views Asked by At

I want to avoid printing space (" ") or an underscore ("_") after the last element of an array while printing them using a for loop. The problem mentions one of the way to do so, I want to understand how and why this happens.

    vector<int> v1 = {1, 2, 3, 4, 5};
    int n = v1.size();
    for (int i = 0; i < n; i++) {
        cout << v1[i] << "_"[i==n-1];
    }

this prints 1_2_3_4_5 which is as expected.

I want to get around the working of this operator. How it functions and what other aspects does it cover.

3

There are 3 answers

2
463035818_is_not_an_ai On BEST ANSWER

There is nothing special going on with [] here. Its the usual element access. "_" is a const char [2] where first character is _ and the second is a null terminator \0.

You could do the same with a string:

vector<int> v1 = {1, 2, 3, 4, 5};
int n = v1.size();
for (int i = 0; i < n; i++) {
    cout << v1[i] << std::string{"_"}[i==n-1];
}

Note, that std::string is an excepetion among containers and it is ok to read the \0 from str[str.size()].

\0 is not a printable character, hence you see the output you see.

Perhaps the code more clear if you use an empty string explicitly:

vector<int> v1 = {1, 2, 3, 4, 5};
std::vector<std::string> d{ "_",""};
int n = v1.size();
for (int i = 0; i < n; i++) {
    cout << v1[i] << d[i==n-1];
}

Alternatively, as you are using an index based loop anyhow (rather than a range based loop, which should be prefered), you can start the loop at the second element:

std::vector<int> v1 = {1, 2, 3, 4, 5};
std::vector<std::string> d{ "_",""};
int n = v1.size();
if (n) std::cout << v1[0];
for (int i = 1; i < n; i++) {
    std::cout << "_" << v1[i];
}
1
Vlad from Moscow On

In this statement

cout << v1[i] << "_"[i==n-1];

the expression "_"[i==n-1] is an expression with the string literal "_".

String literals have types of constant character arrays. The literal "_" has the type const char[2] and is represented in memory like

{ '_', '\0' }

So the expression "_"[i==n-1] uses the subscript operator with the string literal that is with an array. The expression i == n-1 is always equal to false that is converted to integer 0 when i is not equal to n-1. Otherwise it is equal to true that is converted to 1.

So you have either "_"[0] that yields the character '_' or "_"[1] that yields the character '\0';.

That is when i is not equal to n-1 you in fact have

cout << v1[i] << '_';

and when i is equal to n -1 you in fact have

cout << v1[i] << '\0';

Pay attention to that in this expression "_"[i==n-1] the string literal is implicitly converted to pointer to its first element.

Alternatively you could write with the same effect

vector<int> v1 = {1, 2, 3, 4, 5};
int n = v1.size();
const char literal[] = "_";
for (int i = 0; i < n; i++) {
    cout << v1[i] << literal[i==n-1];
}

To enlarge your knowledge bear in mind that this expression "_"[i==n-1] you may also rewrite like ( i == n-1 )["_"] though such an expression will only confuse readers of the code.

From the C++17 Standard (8.2.1 Subscripting)

1 A postfix expression followed by an expression in square brackets is a postfix expression. One of the expressions shall be a glvalue of type “array of T” or a prvalue of type “pointer to T” and the other shall be a prvalue of unscoped enumeration or integral type. The result is of type “T”. The type “T” shall be a completely-defined object type.66 The expression E1[E2] is identical (by definition) to *((E1)+(E2)) [ Note: see 8.3 and 8.7 for details of * and + and 11.3.4 for details of arrays. — end note ] , except that in the case of an array operand, the result is an lvalue if that operand is an lvalue and an xvalue otherwise. The expression E1 is sequenced before the expression E2.

Pay attention to that this code snippet

vector<int> v1 = {1, 2, 3, 4, 5};
int n = v1.size();
for (int i = 0; i < n; i++) {
    cout << v1[i] << "_"[i==n-1];
}

could look more clear using the range-based for loop. For example

std::vector<int> v1 = {1, 2, 3, 4, 5};

bool next = false;
for ( const auto &item : v1 ) 
{
    if ( next ) std::cout << '_';
    else next = true; 

    std::cout << item;
}

If the compiler supports the C++20 Standard then you can write also like

std::vector<int> v1 = {1, 2, 3, 4, 5};

for ( bool next = false; const auto &item : v1 ) 
{
    if ( next ) std::cout << '_';
    else next = true; 

    std::cout << item;
}
0
Pepijn Kramer On

your problem has nothing todo with the subscript operator though you just need to figure out when to print the _ or not based on the position in the array when printing.

#include <vector>
#include <iostream>
    
// using namespace std; ehr no don't do this
  
int main()
{
    std::vector<int> v1{ 1, 2, 3, 4, 5 };
   
    for (std::size_t i{ 0ul }; i < v1.size(); ++i)
    {
        if (i != 0ul) std::cout << "_";
        std::cout << v1[i];
    }
    std::cout << "\n";
   
    // with vector use range based for if you can
    // and then it becomes
    bool print_underscore = false;
    
    for (const int value : v1)
    {
        if (print_underscore) std::cout << "_";
        std::cout << value;
        print_underscore = true;
    }
    std::cout << "\n";
    
    return 0;
}