Статическое утверждение размера массива std ::, тип которого получен с использованием decltype из возвращаемого значения функции-члена - PullRequest
0 голосов
/ 20 апреля 2019

(Извиняюсь за громоздкий заголовок; я не знаю, как более кратко изложить эту проблему. Если у кого-то есть идея получше, пожалуйста, не стесняйтесь редактировать!)

Я хочу написать бесплатную функцию, которая может автоматически определять тип ее параметра на основе возвращаемого значения функции-члена класса. Используя decltype, эта часть проста.

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

Рассмотрим следующее MCVE:

#include <type_traits>
#include <array>
#include <iostream>

class Foo
{
public:
   std::array<int, 10> Get();
};

void PrintFoos(const decltype(Foo().Get())& param)
{
    static_assert(param.size() == 10, "wrong size");
    for (const auto& i : param)
    {
        std::cout << i << "\n";
    }
}

GCC компилирует вышеуказанный код просто отлично, без предупреждения.

Сланг, с другой стороны, ржет:

error: static_assert expression is not an integral constant expression
    static_assert(param.size() == 10, "wrong size");
                  ^~~~~~~~~~~~~~~~~~

Так же, как и MSVC:

(13): error C2131: expression did not evaluate to a constant
(13): note: failure was caused by a read of a variable outside its lifetime
(13): note: see usage of 'param'

Почему GCC компилирует это нормально, когда другие компиляторы отклоняют его? Есть ли у меня расширение GCC для поддержки этого?

Что говорит об этом языковой стандарт? Я нацеливаюсь на C ++ 17, но мне также было бы интересно узнать, есть ли какие-либо изменения по сравнению с C ++ 14.

Бонусный вопрос: Есть ли способ, которым я могу изменить этот код, чтобы он работал? Очевидно, что static_assert должен завершиться неудачей, если выражение decltype не соответствует типу std::array, поскольку функция-член size() не будет constexpr. Я полагаю, что есть решение, включающее добавление вспомогательной функции шаблона, но я бы не стал добавлять другое определение функции, если в этом нет крайней необходимости.

1 Ответ

5 голосов
/ 20 апреля 2019

Я считаю, что clang и другие (icc и MSVC) здесь технически правильны, а GCC ошибочен. static_assert-объявление принимает константное выражение [expr.const] / 2 . Я считаю, что соответствующая формулировка C ++ 17 для рассматриваемого случая должна быть [expr.const] /2.11:

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

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

Выражение в вашем static_assert выше явно делает именно это, однако (param является id-выражением , которое относится к переменной ссылочного типа, ни одно из исключений не применяется). Таким образом, это не константное выражение, и программа некорректна [dcl.dcl] / 6 . Соответствующая формулировка в стандарте C ++ 14 представляется идентичной. Я считаю это ошибкой в ​​GCC.

Если вы можете изменить свою функцию в шаблон, вы можете просто определить размер:

template <int N>
void PrintFoos(const std::array<int, N>& param)
{
    …
}

В качестве альтернативы, если вы хотите, чтобы все зависело от Foo, вы также можете просто определить открытую константу и получить тип массива и т. Д. Из этого:

class Foo
{
public:
    static constexpr auto size = 10;
    std::array<int, size> Get();
};

void PrintFoos(const decltype(Foo().Get())& param)
{
    static_assert(Foo::size == 10, "wrong size");
}

И, конечно, вы можете использовать вспомогательный шаблон:

template <typename T>
constexpr std::size_t deduce_array_size = 0U;

template <typename T, std::size_t N>
constexpr std::size_t deduce_array_size<std::array<T, N>> = N;

template <typename T>
constexpr std::size_t deduce_array_size<T&> = deduce_array_size<T>;

template <typename T>
constexpr std::size_t deduce_array_size<T&&> = deduce_array_size<T>;

, а затем

void PrintFoos(const decltype(Foo().Get())& param)
{
    static_assert(deduce_array_size<decltype(param)> == 10, "wrong size");
}

Наконец, еще один вариант (вдохновленный комментарием Якка - Адама Невраумона ниже) будет просто создать значение типа массива в вашем константном выражении и запросить его размер:

static_assert(std::decay_t<decltype(param)>{}.size() == 10, "wrong size");
...