Как решить проблему «чтение переменной non-constexpr 'a' не допускается в постоянном выражении» с boost.hana - PullRequest
8 голосов
/ 25 февраля 2020

Я использую c ++ 17 с Boost.hana для написания некоторых программ метапрограммирования. Одна проблема, которая меня поразила, это то, какое выражение можно использовать в контексте constexpr, например static_assert. Вот пример:

#include <boost/hana.hpp>

using namespace boost::hana::literals;

template <typename T>
class X {
public:
    T data;

    constexpr explicit X(T x) : data(x) {}

    constexpr T getData() {
        return data;
    }
};


int main() {
    {   // test1
        auto x1 = X(1_c);
        static_assert(x1.data == 1_c);
        static_assert(x1.getData() == 1_c);
    }
    {   //test2.1
        auto x2 = X(boost::hana::make_tuple(1_c, 2_c));
        static_assert(x2.data[0_c] == 1_c);

        // static_assert(x2.getData()[0_c] == 1_c); // read of non-constexpr variable 'x2' is not allowed in a constant expression
    }
    {   //test2.2
        auto x2 = X(boost::hana::make_tuple(1_c, 2_c));
        auto data = x2.getData();
        static_assert(data[0_c] == 1_c);
    }
}

Сначала я пишу класс X с полем data и средством доступа getData () . В main () ' test1 part x1.data и x1.getData () ведут себя так же, как и ожидалось. Но в части test2 , изменяющей аргумент на кортеж boost :: hana, static_assert(x2.data[0_c] == 1_c) по-прежнему ведет себя нормально, но static_assert(x2.getData()[0_c] == 1_c) завершается неудачно при компиляции с ошибкой « чтение переменной не-constexpr» x2 'не допускается в константном выражении '. Что утомляет, если я разделю x2.getData()[0_c] на auto data = x2.getData(); и static_assert(data[0_c] == 1_c);, то он снова хорошо скомпилируется. Я ожидаю, что они ведут себя одинаково. Так может кто-нибудь помочь объяснить, почему x2.getData()[0_c] нельзя использовать в static_assert в этом примере?

Воспроизвести: clang ++ 8.0 -I / path / to / hana-1.5.0 / include -std = c ++ 17 Test. cpp

Ответы [ 3 ]

5 голосов
/ 02 марта 2020

Проблема в том, что boost::hana::tuple не имеет конструктора копирования.

У него есть конструктор , который выглядит как конструктор копирования:

template <typename ...dummy, typename = typename std::enable_if<
    detail::fast_and<BOOST_HANA_TT_IS_CONSTRUCTIBLE(Xn, Xn const&, dummy...)...>::value
>::type>
constexpr tuple(tuple const& other)
    : tuple(detail::from_index_sequence_t{},
            std::make_index_sequence<sizeof...(Xn)>{},
            other.storage_)
{ }

Но поскольку это шаблон, он не является конструктором копирования .

Поскольку boost::hana::tuple не имеет конструктора копирования, один из них объявлен неявно и определено как значение по умолчанию (оно не подавляется, поскольку boost::hana::tuple не имеет каких-либо конструкторов копирования или перемещения или операторов присваивания, поскольку, как вы уже догадались, они не могут быть шаблонами).

Здесь мы видим реализацию расхождения , продемонстрированную в поведении следующей программы:

struct A {
    struct B {} b;
    constexpr A() {};
    // constexpr A(A const& a) : b{a.b} {}    // #1
};
int main() {
    auto a = A{};
    constexpr int i = (A{a}, 0);
}

g cc принимает, в то время как Clang и MSV C отклоняют, но принимают, если строка #1 не прокомментировано. То есть компиляторы не согласны с тем, допустимо ли использование неявно определенного конструктора копирования не (непосредственно) пустого класса в контексте постоянной оценки.

В соответствии с определением неявно - определенный конструктор копирования , нет никакого способа, которым # 1 отличается от constexpr A(A const&) = default;, поэтому g cc является правильным. Также обратите внимание, что если мы дадим B пользовательский конструктор копирования constexpr, который Clang и MSV C снова примут, то проблема состоит в том, что эти компиляторы не могут отследить конструктивность копий constexpr рекурсивно пустых неявно копируемых классов. Поданные ошибки для MSV C и Clang ( исправлено для Clang 11).

Обратите внимание, что использование operator[] является красная сельдь; вопрос заключается в том, позволяют ли компиляторы вызывать getData() (который копирует-конструирует T) в контексте постоянной оценки, таком как static_assert.

Очевидно, что идеальным решением будет Boost. Hana исправить boost::hana::tuple так, чтобы он имел фактические конструкторы копирования / перемещения и операторы назначения копирования / перемещения. (Это исправит ваш вариант использования, так как код будет вызывать предоставленные пользователем конструкторы копирования, которые допустимы в контексте постоянной оценки.) В качестве обходного пути вы можете рассмотреть взлом getData() для обнаружения случая без состояния T:

constexpr T getData() {
    if (data == T{})
        return T{};
    else
        return data;
}
1 голос
/ 28 февраля 2020

Проблема в том, что вы пытаетесь получить значение времени выполнения и проверить его при компиляции.

Что вы можете сделать, это принудительно заставить выражение во время компиляции через decltype, и оно будет работать как a charm:).

static_assert(decltype(x2.getData()[0_c]){} == 1_c);

#include <boost/hana.hpp>

using namespace boost::hana::literals;

template <typename T>
class X {
public:
    T data;

   constexpr explicit X(T x) : data(x) {}

   constexpr T getData() {
        return data;
    }
};


int main() {
    {   // test1
        auto x1 = X(1_c);
        static_assert(x1.data == 1_c);
        static_assert(x1.getData() == 1_c);
    }
    {   //test2
        auto x2 = X(boost::hana::make_tuple(1_c, 2_c));
        static_assert(x2.data[0_c] == 1_c);

         static_assert(decltype(x2.getData()[0_c]){} == 1_c);

        auto data = x2.getData();
        static_assert(data[0_c] == 1_c);
    }
}

Теперь выражение вычисляется во время компиляции, поэтому тип известен во время компиляции и, так как он также является конструируемым во время вычисления, можно использовать в static_assert

0 голосов
/ 28 февраля 2020

Итак, во-первых, в методе getData() отсутствует метод const, поэтому должно быть:

constexpr T getData() const

Ни одна переменная не повышена, по крайней мере, со стандартной точки зрения, до constexpr если он не помечен как constexpr.

Обратите внимание, что это не обязательно для x1 типа X, специализирующегося на hana :: integra_constant, поскольку результат 1_c - это тип без пользовательской копии конструктор, который не содержит никаких внутренних данных, поэтому операция копирования в getData() на самом деле является запретной операцией, поэтому выражение: static_assert(x1.getData() == 1_c); хорошо, так как фактическое копирование не выполняется (и нет доступа к неконстантным this указатель x1 необходим).

Это очень отличается для вашего контейнера с hana::tuple, который содержит фактическую структуру копирования hana::tuple из данных в поле x2.data. Это требует фактического доступа к вашему this указателю - который не был необходим в случае x1, который также не был переменной constexpr.

Это означает, что вы неправильно указали свое намерение с обоими x1 и x2, и необходимо, по крайней мере, для x2, пометить эти переменные как constexpr. Также обратите внимание, что использование пустого кортежа, который является в основном пустой (не определяемые пользователем конструкторы копирования) общей hana::tuple, работает без проблем (раздел test3):

#include <boost/hana.hpp>

using namespace boost::hana::literals;

template <typename T>
class X {
public:
    T data;

    constexpr explicit X(T x) : data(x) {}

    constexpr T getData() const {
        return data;
    }
};

template<typename V>
constexpr auto make_X(V value)
{
    return value;
}

int main() {
    {   // test1
        auto x1 = X(1_c);
        static_assert(x1.data == 1_c);
        static_assert(x1.getData() == 1_c);
    }
    {   //test2
        constexpr auto x2 = X(boost::hana::make_tuple(1_c, 2_c));
        static_assert(x2.data[0_c] == 1_c);

        static_assert(x2.getData()[0_c] == 1_c); // read of non-constexpr variable 'x2' is not allowed in a constant expression

        auto data = x2.getData();
        static_assert(data[0_c] == 1_c);
    }
    {   //test3
        auto x3 = X(boost::hana::make_tuple());
        static_assert(x3.data == boost::hana::make_tuple());

        static_assert(x3.getData() == boost::hana::make_tuple());
    }
}
...