Это ошибка в том, как Clang реализует __builtin_memcmp? - PullRequest
0 голосов
/ 02 апреля 2020

Хорошо, вот код:

#include <cstdio>
#include <cstdint>

template<typename T>
[[nodiscard]] constexpr bool
run_tests() noexcept
{
    T const blah1[6]{4, 8, 15, 16, 23, 42};
    T const blah2[6]{4, 8, 15, 16, 23, 42};
    T const blah3[6]{};

    if (__builtin_memcmp(blah1, blah2, 6 * sizeof(T)) != 0) {
        return false;
    }

    if (__builtin_memcmp(blah1, blah3, 6 * sizeof(T)) == 0) {
        return false;
    }

    return true;
}

int
main() noexcept
{
    static_assert(run_tests<int8_t>());
    // static_assert(run_tests<int64_t>());

    if (run_tests<int8_t>()) {
        printf("success\n");
    }
    else {
        printf("failure\n");
    }

    if (run_tests<int64_t>()) {
        printf("success\n");
    }
    else {
        printf("failure\n");
    }

    return 0;
}

С Clang 10 (на Windows), это прекрасно компилируется, и если я его запускаю, я получаю:

success
success

Как я и ожидал. Если я раскомментирую следующее:

static_assert(run_tests<int64_t>());

, я получаю следующую ошибку во время компиляции:

error: static_assert expression is not an integral constant expression

Здесь есть две странные вещи. Во-первых, это нормально компилируется с int8_t и uint8_t, но любой другой целочисленный тип, который я предоставляю, воспроизводит ошибку выше. Во-вторых, если я закомментирую это:

if (__builtin_memcmp(blah1, blah3, 6 * sizeof(T)) == 0) {
    return false;
}

Он прекрасно компилируется и со всеми целочисленными типами. Так что это только тот случай, когда массивы не равны и не являются 8-битными, что я получаю эту ошибку во время компиляции.

Мысли? Мне это кажется ошибкой, но я хотел посмотреть, что думают другие, прежде чем я отправлю отчет об ошибке. Для справки я попытался скомпилировать с включенными C ++ 17 и C ++ 20, и я убедился, что Clang 10 реализует __builtin_memcmp в качестве constexpr (как с помощью макроса __has, так и благодаря тому, что он прекрасно компилируется как constexpr в какой-то сценарий ios).

Ответы [ 2 ]

3 голосов
/ 03 апреля 2020

Технически это не ошибка. Не задокументировано, при каких условиях __builtin_memcmp является constexpr.

Обратите внимание, что другой ответ с пометкой «чтение разыменованного указателя« один конец »не допускается в константном выражении» от старая версия clang, для которой __builtin_memcmp следует другим правилам.

Реальность такова, что __builtin_memcmp в clang 10 является constexpr, если выполняются следующие условия:

  • предоставленная длина 0 или
  • типы являются целыми и
    • размер обоих типов составляет 1 или
    • ответ равен 0, т. Е. Достаточное количество сравниваемых элементов равно

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

Это относится к обоим наблюдениям:

  • это прекрасно компилируется с int8_t и uint8_t - потому что они имеют размер 1
  • , если я закомментирую это (!=) - потому что равенство является constexpr

ref: https://reviews.llvm.org/D55510

1 голос
/ 02 апреля 2020

Не реальный ответ, но трудно добавить сообщения об ошибках компилятора в комментарий. Может помочь вам

С версией Clang 9.0.1, я вижу то же поведение.

С более старой версией AppleClang (Apple LLVM версия 10.0.0 (clang-1000.11.45.5)) I получите это, даже если я закомментирую материал blah3 ...

z.cpp:27:19: error: static_assert expression is not an integral constant expression
    static_assert(run_tests<int64_t>());
                  ^~~~~~~~~~~~~~~~~~~~
z.cpp:12:9: note: read of dereferenced one-past-the-end pointer is not allowed in a constant expression
    if (__builtin_memcmp(blah1, blah2, 6 * sizeof(T)) != 0) {
        ^
z.cpp:27:19: note: in call to 'run_tests()'
    static_assert(run_tests<int64_t>());
                  ^
1 error generated.

Кроме того, я посмотрел, и libc ++ использует его здесь, так что я думаю, что ваши предположения, основанные на документации, верны.

inline _LIBCPP_CONSTEXPR_AFTER_CXX14
int
char_traits<char>::compare(const char_type* __s1, const char_type* __s2, size_t __n) _NOEXCEPT
{
    if (__n == 0)
        return 0;
#if __has_feature(cxx_constexpr_string_builtins)
    return __builtin_memcmp(__s1, __s2, __n);
#elif _LIBCPP_STD_VER <= 14
    return memcmp(__s1, __s2, __n);
#else
    for (; __n; --__n, ++__s1, ++__s2)
    {
        if (lt(*__s1, *__s2))
            return -1;
        if (lt(*__s2, *__s1))
            return 1;
    }
    return 0;
#endif
}

FWIW, я добавил это к примеру ...

static_assert(__has_feature(cxx_constexpr_string_builtins));
...