C++ Конфликт функции преобразования и перегруженного оператора взятия индекса

Доброго времени суток!

Возникла такая проблема: появилась нужда сотворить самописный класс String, который будет поддерживать взятие индекса (возвращает char) и который можно будет неявно приводить к типу char*.

Проблема возникает тогда, когда всё организовано и пытаюсь взять индекс. Выдаёт такое сообщение (ошибка компиляции):

существует несколько операторов "[]", соответствующих этим операндам:
    встроенный оператор "pointer-to-object[integer]"
    функцию "String::operator[](size_t index) const"

    типы операндов: String [ int ]

Как я понял, он в попытках взять элемент под индексом видит два пути:

  1. неявно преобразовать String к char* и от него уже взять элемент под каким-либо индексом

  2. использовать перегруженный оператор

Подскажите, пожалуйста, как сделать так, чтобы он однозначно использовал при взятии индекса конкретно перегруженный operator[]

Вот код класса (неполный, ибо класс достаточно большой, но уверяю, проблема в тех строчках):

String.h:

#ifndef CLASS_STRING
#define CLASS_STRING


class String {
public:
    String(const char* str);
    char& operator[](size_t index) const;
    operator char* () const;
private:
    size_t size_;
    size_t capacity_;
    char* pointer_;
};

#endif

String.cpp:

#include "String.h"
#include <iostream>

String::String(const char* str)
{
    size_ = 0;
    while (str[size_] != '\0') {
        ++size_;
    }
    capacity_ = size_ + 1;
    pointer_ = new char[capacity_];
    
    for (unsigned int i = 0; i < capacity_; ++i) {
        pointer_[i] = str[i];
    }
}

char& String::operator[](size_t index) const
{
    if (index >= size_) {
        throw std::out_of_range("IndexError: String index out of range");
    }
    return pointer_[index];
}

String::operator char* () const
{
    return pointer_;
}

main.cpp:

#include <iostream>
#include "String.h"

int main() {
    String name = "123";
    std::cout << name[0] << std::endl;
}

Заранее благодарю


P.S. на скриншотах основной файл Pets.cpp - отголоски старого проекта

Скриншот с ошибками - основной файл Скриншот с ошибками - файл хедера


Ответы (1 шт):

Автор решения: KoVadim

Ок, давайте попробуем разобраться, что же там происходит. Объявив оператор каста к указателю на строку, Вы открыли дыру в безопасности типов. Дело в том, что у указателей по умолчанию есть объявленный неявный оператор [] - operator[](const char *, int). Видите главную его особенность? там int, а у вас - size_t.

size_t - это "магический тип", то есть тип, который компилятор сам себе определяет внутри, главное, что бы он соответствовал стандарту.

std::size_t can store the maximum size of a theoretically possible object of any type (including array).

int это тоже "магический тип". да да, это один из самых старых магических типов и компилятор даже по умолчанию, когда видит отсутствие типа, считает, что там int.

По очевидным причинам, скорее всего на 32 битной платформе это будет 32битное число, а на 64 битной - 64битное (еще раз - скорее всего. компилятор, зная особенности конкретной платформы, может решить по своему усмотрению, но на распространённых платформах это скорее всего так).

А вот int не повезло. на 64битной платформе он 32битный. И там он совпадает с size_t по размеру. и компилятор не может решить, какую с перегрузок ему выбрать - пользовательский оператор [] или оператор приведения к char* и у него уже встроенный оператор [] (как по мне, то это странная идея, но разработчикам стандарта виднее). А вот на 64битной платформе этой проблемы нет - там типы имеют разный размер и проблемы не возникает.

когда же написать вот так name[0u], то мы явно указываем, что у нас там целое беззнаковое, а это автоматом заставляет использовать перегрузку для size_t

Такая перегрузка ещё и опасна сама по себе. Например, можно так

#include <iostream>

void foo(char* s) {
}

class String {
    public:
    operator char*() { return nullptr;}
};

int main()
{
    String s;
    foo(s);
}

Как по мне, это супер-опасный код. функция foo может поменять внутри все.

Видимо это все и привело к тому, что в стандартной библиотеке это сделали явным - c_str().

→ Ссылка