cpp-ru / ideas

Идеи по улучшению языка C++ для обсуждения
https://cpp-ru.github.io/proposals
Creative Commons Zero v1.0 Universal
89 stars 0 forks source link

Управление квалификаторами методов #481

Closed infy-infy closed 2 years ago

infy-infy commented 2 years ago
  1. Предлагаю введение нового ключевого слова declqual для вычисления квалификаторов метода. declqual(T) должен "возвращать" квалификаторы, принимая на вход тип. Рассмотрим механику работы на следующем примере:
template<typename T>
class Wrapper
{
public:
    T get() declqual(T) { return _value;}
private:
    T _value;
};

В данном случае, если T не является ссылкой или является lvalue ссылкой, то квалификатор метода будет &. Если T это константная ссылка (строго говоря, тип const T& или const T&& это ссылка на константный тип, но все привыкли говорить "константная ссылка"), то квалификаторы будут, соответственно, const & или const &&. Если T это rvalue ссылка, то квалификатор будет &&.

  1. Выражение declqual(auto) будет создавать все варианты квалификаторов по мере того, как компилятор будет натыкаться на очередной вариант использования. На самом деле, юзкейс этого выражения и является основным мотиватором этой идеи: с помощью этого выражения можно будет избавиться от дублирования кода или использования уродливого const_cast в геттерах. Например:

Раньше:

class Wrapper
{
public:
auto& get_by_key(std::string_view k, SomeType& def) const {
    if (auto it = _map.find(k); it != _map.end()) {
       return it->second;
    }
    return def;
}

// Non-const version 1: code duplicate
auto& get_by_key(std::string_view k, SomeType& def) {
    if (auto it = _map.find(k); it != _map.end()) {
       return it->second;
    }
    return def;
}

// Non-const version 2: ugly const casts
auto& get_by_key(std::string_view k, SomeType& def) {
    auto& result = const_cast<const Wrapper*>(this)->getByKey(k, def);
    return const_cast<SomeType&>(result);
}
private:
    std::map<std::string, SomeType, std::less<>> _map;
};

Стало

class Wrapper
{
public:
auto& get_by_key(std::string_view k, SomeType& def) declqual(auto) & {
    if (auto it = _map.find(k); it != _map.end()) {
       return it->second;
    }
    return def;
}
private:
    std::map<std::string, SomeType, std::less<>> _map;
};

Как можно заметить, в последнем примере стоит & после выражения: я предлагаю оставлять возможность сокращать количество вариаций за счет явного указания квалификаторов после выражение declqual. В примере выше это означает, что get_by_key может иметь квалификаторы только const& или & (ну и формально, конечно, варианты с volatile). Явное указание может производится либо непосредственной подстановкой руками, либо с помощью того же declqual. Например:

template<typename T>
class Wrapper
{
public:
    // instead of conditional_t there could be some magical trait that removes cv qualifiers from references
    T get() declqual(auto) declqual(std::conditional_t<std::std::is_rvalue_reference_v<T>, int&&, int&>) { return _value;}
private:
    T _value;
};

В данном случае второй declqual говорит о том, что ссылочный квалификатор get() может быть либо && (если T - это rvalue ссылка), либо & (когда тип lvalue ссылка или не ссылка). Поэтому declqual(auto) может варьировать только cv квалификаторы для конкретного класса.

объявление T get() declqual(auto) const volatile & не должно считаться ошибкой, т.к. в такое может "вычислиться" declqual(...) в шаблонном коде. Тогда declqual(auto) тут не будет иметь никакого эффекта, т.к. все квалификаторы указаны явно.

Однако этого может оказаться недостаточно, если мы хотим явно запрещать cv квалификаторы. По аналогии с consteval предлагаю ввести подобную запись: T get() declqual(auto) !volatile & - это означает, что варьироваться может только const квалификатор, а volatile всегда отсутствует. Разумеется, разрешить подобное только при использовании declqual(auto), чтобы нельзя было обычные методы помечать как !const или !volatile.

Я вижу данный инструментарий логичным продолжением функциональности explicit(bool), которая убивает сразу двух зайцев - во-первых, дает возможность управлять квалификаторами методов из шаблонного кода, во-вторых - избавляет язык от проблемы геттеров. Однако нужно придумать более мощные примеры полезного использования declqual(T), текущие демонстрируют только принцип работы.

Izaron commented 2 years ago

Интересное предложение. Оно может сработаться в группе с propconst [P1974]

Он предлагался еще в 2015-2016, и возродился в 2020, у него юзкейс таков, что без него не заработает non-transient память из constexpr.

propconst это квалификатор для указательных/ссылочных типов:

T propconst*
T propconst&

Если T константный, то он конвертируется в const, иначе просто пропадает

int propconst * ---> int *
int propconst * const ---> int const * const

Если это предложение примут, то будет дублирование и два get вместо одного

template <typename T>
struct unique_ptr
{
    T* get(); // Resolves to U* if T is propconst U
    T* const get() const; // Resolves to const U* if T is propconst U
};

Надо сделать так, чтобы был один, с declqual

dkatkevich-ms commented 2 years ago

В грядущем стандарте уже приняли deducing this - новый синтаксис определения метода, который покрывает ваш юзкейс

apolukhin commented 2 years ago

Приняли в стандарт C++23 https://wg21.link/P0847