Пользовательский оператор преобразования типов не работает при вызове по ссылке пересылки (работает, когда объект передается по значению) - PullRequest
5 голосов
/ 27 сентября 2019

Я не могу понять суть этой ошибки, так что извините, если название может быть лучше.Этот код не может быть скомпилирован:

template <auto v>
struct value_as_type {
    using type = decltype(v);    
    static constexpr type value {v};
    constexpr operator type() const {
        return v;
    }
};

template <int First, int Last, typename Functor>
constexpr void static_for([[maybe_unused]] Functor&& f)
{
    if constexpr (First < Last)
    {
        f(value_as_type<First>{});
        static_for<First + 1, Last, Functor>(std::forward<Functor>(f));
    }
}

template <class... FieldsSequence>
struct DbRecord
{
private:
    static constexpr bool checkAssertions()
    {
        static_assert(sizeof...(FieldsSequence) > 0);
        static_for<1, sizeof...(FieldsSequence)>([](auto&& index) {
            constexpr int i = index;
            static_assert(i > 0 && i < sizeof...(FieldsSequence));
        });

        return true;
    }

private:
    static_assert(checkAssertions());
};

Строка с ошибкой - constexpr int i = index;, и ошибка «выражение не вычислилось как константа».

Почему это так?Я ожидаю, что будет вызван оператор преобразования объекта value_as_type<int>.И что наиболее странно, он работает просто отлично, если лямбда принимает auto index, а не auto&& index.

Онлайн-демонстрация: https://godbolt.org/z/TffIIn

1 Ответ

2 голосов
/ 27 сентября 2019

Вот более короткое воспроизведение, рассмотрим разницу между программой, скомпилированной с ACCEPT, и программой без:

struct One { constexpr operator int() const { return 1; } };

template <typename T>
constexpr int foo(T&& t) {
#ifdef ACCEPT
    return t;
#else
    constexpr int i = t;
    return i;
#endif
}

constexpr int i = foo(One{});

Как можно предположить при выборе макроса, случай ACCEPT в порядке, идругой случай плохо сформирован.Почему?Рассматриваемое правило: [expr.const] /4.12:

Выражение e является основным константным выражением , если только оценка e, следуя правилам абстрактной машины, оценил бы одно из следующего: [...] id-выражение , которое ссылается на переменную или член данных ссылочного типа, если ссылка не имеет предшествующей инициализациии либо [...]

Что такое до инициализации ?Прежде чем я отвечу, давайте предоставим другую программу и пройдемся по ее семантике:

Противоречие

struct Int { constexpr operator int() const { return i; } int i; };
template <int> struct X { };

template <typename T>
constexpr auto foo(T&& t) {
    constexpr int i = t;
    return X<i>{};
}

constexpr auto i = foo(Int{1});
constexpr auto j = foo(Int{2});

Есть только одна функция foo<Int>, поэтому онадолжен иметь один конкретный тип возврата.Если бы эта программа была разрешена, то foo(Int{1}) вернул бы X<1>, а foo(Int{2}) вернул бы X<2> - то есть foo<Int> может вернуть разные типы?Этого не может быть, поэтому это должно быть неправильно сформировано.

Наименьшее возможное поле

Когда мы находимся в ситуации, требующей постоянного выражения, воспринимайте это как открытие нового окна,Все в этом поле должно соответствовать правилам постоянной оценки, как если бы мы только начали с этого момента.Если нам требуется новое константное выражение, вложенное в этот блок, мы открываем новый блок.Ящики полностью вниз.

И в исходном воспроизведении (с One), и в новом воспроизведении (с Int) у нас есть это объявление:

constexpr int i = t;

Это открываетновая коробка.Инициализатор t должен удовлетворять ограничениям константных выражений.t является ссылочным типом, но не имеет предшествующей инициализации в этом поле , следовательно, оно некорректно сформировано.

Теперь в принятом случае:

struct One { constexpr operator int() const { return 1; } };

template <typename T>
constexpr int foo(T&& t) {
    return t;
}

constexpr int i = foo(One{});

У нас есть только одно поле: инициализация глобальной i.Внутри этого блока мы по-прежнему оцениваем id-выражение ссылочного типа в пределах этого return t;, но в этом случае у нас do есть предшествующая инициализация внутри нашего блока: у нас естьвидимость, где мы связываем t с One{}.Так что это работает.Нет противоречия, которое можно построить из этих правил.В самом деле, это тоже было бы хорошо:

constexpr int j = foo(Int{1});
constexpr int k = foo(Int{2});
static_assert(i+k == 3);

Поскольку у нас все еще просто один вход каждый раз в постоянную оценку, и в этой оценке ссылка t имеет предшествующую инициализацию, и члены Int также можно использовать в константных выражениях.

Назад к OP

Удаление ссылки работает, потому что мы больше не нарушаем ограничение ссылки, и нет никаких других ограничений, которые мы могли бы нарушить.Мы не читаем ни одно переменное состояние или что-то еще, функция преобразования просто возвращает константу.

Аналогичный пример, в котором мы пытались передать Int{1} в foo по значению, все равно не получится - на этот раз не для ссылочного правила, а вместо этого для правила преобразования lvalue-to-rvalue.По сути, мы читаем что-то, что нам нельзя разрешить читать - потому что мы в конечном итоге столкнулись с тем же противоречием, состоящим в возможности создания функции с несколькими типами возвращаемых значений.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...