constexpr std :: string_view :: find_last_of не работает на clang 8 с libstdc ++ 9 - PullRequest
3 голосов
/ 06 июня 2019

Следующий код компилируется под g ++ 9 с флагом -std=c++17, но не clang 8 с тем же флагом:

#include <string_view>
#include <cstdlib>


int main() {

        constexpr std::string_view hello = "hello";
        constexpr size_t last_l = hello.find_last_of("lo");
}

Сообщение об ошибке выглядит следующим образом:

test.cpp:8:19: error: constexpr variable 'last_l' must be initialized by a constant expression
        constexpr size_t last_l = hello.find_last_of("lo");
                         ^        ~~~~~~~~~~~~~~~~~~~~~~~~
/usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/char_traits.h:349:9: note: cast from 'void *' is not allowed in a constant expression
        return static_cast<const char_type*>(__builtin_memchr(__s, __a, __n));
               ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/string_view.tcc:150:12: note: in call to 'find(&"lo"[0], 2, "hello"[4])'
              if (traits_type::find(__str, __n, this->_M_str[__size]))
                  ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/string_view:402:22: note: in call to '&hello->find_last_of(&"lo"[0], 18446744073709551615, 2)'
      { return this->find_last_of(__str, __pos, traits_type::length(__str)); }
                     ^
test.cpp:8:34: note: in call to '&hello->find_last_of(&"lo"[0], 18446744073709551615)'
        constexpr size_t last_l = hello.find_last_of("lo");
                                        ^
test.cpp:10:16: error: static_assert expression is not an integral constant expression
        static_assert(last_l == 3);
                      ^~~~~~~~~~~
test.cpp:10:16: note: initializer of 'last_l' is not a constant expression
test.cpp:8:19: note: declared here
        constexpr size_t last_l = hello.find_last_of("lo");
                         ^
2 errors generated.

Глядя на сообщение об ошибке, похоже, что оно вызывает std::char_traits<char>::find, что в соответствии с cppreference должно быть constexpr, а не проблемой. Однако, глядя на реализацию в char_traits.h, пока она помечена constexpr, похоже, что она не следует правилам constexpr:

static _GLIBCXX17_CONSTEXPR const char_type*
find(const char_type* __s, size_t __n, const char_type& __a)
{
  if (__n == 0)
    return 0;
#if __cplusplus >= 201703L
  if (__builtin_constant_p(__n)
      && __builtin_constant_p(__a)
      && __constant_char_array_p(__s, __n))
    return __gnu_cxx::char_traits<char_type>::find(__s, __n, __a);
#endif
  return static_cast<const char_type*>(__builtin_memchr(__s, __a, __n));
}

Тем не менее, g ++, похоже, не имеет проблемы с этим, хотя, как видно из диагностического сообщения clang, __builtin_memchr возвращает void*, который затем преобразуется в char*, что, по-видимому, запрещено ( см. Пул 14 ).

Следуя тому же ходу мыслей, следующий пример также компилируется с g ++, но не clang:

#include <string>

int main() {
        constexpr const char* asdf = "asdf";
        constexpr const char* s_ptr = std::char_traits<char>::find(asdf, 2, 's');
}

с сообщением об ошибке:

test_traits.cpp:5:24: error: constexpr variable 's_ptr' must be initialized by a constant expression
        constexpr const char* s_ptr = std::char_traits<char>::find(asdf, 2, 's');
                              ^       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/char_traits.h:349:9: note: cast from 'void *' is not allowed in a constant expression
        return static_cast<const char_type*>(__builtin_memchr(__s, __a, __n));
               ^
test_traits.cpp:5:32: note: in call to 'find(&"asdf"[0], 2, 's')'
        constexpr const char* s_ptr = std::char_traits<char>::find(asdf, 2, 's');
                                      ^
1 error generated.

Это похоже на ошибку stdc++, из-за которой std::char_traits<_>::find не будет должным образом constexpr, и ошибку g ++ для ее компиляции.

Есть что-то, что я пропускаю? Кроме того, есть ли обходной путь? Я сталкивался с этим, пытаясь использовать magic enum , и хотел бы использовать его в среде clang.

Обновление (6/7/19)

Как указал @cpplearner, кажется, что clang должен взять return __gnu_cxx::char_traits<char_type>::find(__s, __n, __a); Интересно, что не , похоже, является проблемой. Следующий код, который является точно такой же функцией из std::char_traits<char>::find, за исключением того, что без этой константной ветви, все еще прекрасно компилируется в g ++, что заставляет меня задуматься, а не использует ли g ++ этот путь:

#include <cstddef>

static constexpr  const char*
find_noconst(const char* __s, size_t __n, const char& __a)
{
        if (__n == 0)
                return 0;
        return static_cast<const char*>(__builtin_memchr(__s, __a, __n));
}

int main() {
        constexpr const char* asdf = "asdf";
        constexpr const char* s_ptr = find_noconst(asdf, 2, 's');
}

Это все еще компилируется в g ++ и завершается ошибкой в ​​clang с той же ошибкой.

Обновление 2 (6/7/19)

Дополнительные исследования подтверждают комментарий @cpplearner:

#include <cstddef>
#include <string>
#include <iostream>

static constexpr  const char*
branch_test(const char* __s, size_t __n, const char& __a)
{
        if (__n == 0)
                return 0;
        if (__builtin_constant_p(__n)
                        && __builtin_constant_p(__a)
                        && std::__constant_char_array_p(__s, __n))
                return "took branch";

        return "did not take branch";

}

int main() {
        constexpr const char* test = branch_test("asdf", 2, 's');

        std::cout << test << std::endl;

}

С gcc это печатает took branch, но с лязгом печатает did not take branch.

Уточнение еще:

#include <string>
#include <iostream>

int main() {
        constexpr bool test = std::__constant_char_array_p("test", 2);

        std::cout << test << std::endl;
}

Печать 0 на Clang и 1 на g ++.

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

#include <iostream>

constexpr bool is_const_char(const char* a, size_t idx) {
        return __builtin_constant_p(a[idx]);
}

int main() {
        constexpr bool test = is_const_char("test", 0);

        std::cout << test << std::endl;
}

Печать 0 на лямке и 1 на gcc. Самая интересная часть для меня, это то, что на самом деле работает:

#include <iostream>

int main() {
        constexpr const char* str = "test";
        constexpr bool test1 = __builtin_constant_p(str[0]);
        constexpr bool test2 = __builtin_constant_p("test"[0]);


        std::cout << test1 << ", " << test2 << std::endl;
}

Эта программа последовательно печатает 1, 1 на clang и g ++.

...