代码之家  ›  专栏  ›  技术社区  ›  Timo

在编译时拆分字符串会在不同的编译器上产生不同的结果

  •  3
  • Timo  · 技术社区  · 6 年前

    我试图在编译时分割一个字符串。我定义了一个函数 split 喜欢

    #include <array>
    #include <string_view>
    
    template <std::size_t N>
    constexpr std::array<std::string_view, N> split(std::string_view str)
    {
        std::array<std::string_view, N> arr{};
        std::size_t start = 0, end = 0;
    
        for (std::size_t i = 0; i < N && end != std::string_view::npos; i++)
        {
            end = str.find_first_of(',', start);    
            arr[i] = str.substr(start, end - start);
            start = end + 1;
        }
    
        return arr;
    }
    

    使用方法如下:

    constexpr std::string_view str = "one,two,three,four,five";
    constexpr std::array<std::string_view, 5> arr = split<5>(str);
    

    msvc和gcc都可以编译。但是clang已经拒绝了这条代码 std::string_view::find_first_of 不会导致常量表达式(这是编译器错误吗?).

    当我像这样测试结果时:

    int main() 
    {
        std::cout << str << "\n\n";
    
        for (auto i = 0; i < arr.size(); i++)
            std::cout << arr[i] << "\n";
    
        return 0;
    }
    

    msvc打印

    one,two,three,four,five
    
    one
    two
    thr
    e,f
    ur,
    

    而gcc给了我预期的结果

    one,two,three,four,five                                                                                                                                                           
    
    one
    two
    three
    four
    five
    

    我添加了第二个split函数,它与原始函数相同,只是它打印出split函数中的中间子字符串。在这种情况下,msvc和gcc都会打印相同的结果,这是上面的预期结果。

    为什么结果不同?我有没有在什么地方调用UB?

    完整的代码可以找到 here

    编辑

    看起来这是msvc中的一个bug。在运行时调用函数会产生预期的结果:

    int main() 
    {
        std::cout << str << "\n\n";
    
        for (auto i = 0; i < arr.size(); i++)
            std::cout << arr[i] << "\n";
    
        auto arr2 = split<5>(str);    
        for (auto i = 0; i < arr2.size(); i++)
            std::cout << arr2[i] << "\n";
    
        return 0;
    }
    

    编辑2

    当msvc为常量表达式运行解释器时,看起来它内部实际上有一个bug。我添加了另一个函数来访问函数之外的变量:

    constexpr decltype(split<5>(str)) arr = split<5>(str);
    constexpr decltype(split_sizes<5>(str)) arr_sizes = split_sizes<5>(str);
    
    template <std::size_t N>
    constexpr std::array<std::array<std::size_t, 3>, N> split_sizes(std::string_view str)
    {
        std::array<std::array<std::size_t, 3>, N> arr{};
        std::size_t start = 0, end = 0;
    
        for (std::size_t i = 0; i < N && end != std::string_view::npos; i++)
        {
            end = str.find_first_of(',', start);
            auto sub = str.substr(start, end - start);
            arr[i] = { sub.length(), start, end };
            start = end + 1;
        }
    
        return arr;
    }
    
    int main() 
    {    
        for (auto i = 0; i < arr.size(); i++)
            std::cout << arr[i] << "\tlen=" << arr_sizes[i][0] << " start=" << arr_sizes[i][1] << " end=" << arr_sizes[i][2] << "\n";
    
        std::cout << "\n";
        auto arr2 = split<5>(str);
        auto arr_sizes2 = split_sizes<5>(str);
    
        for (auto i = 0; i < arr2.size(); i++)
            std::cout << arr2[i] << "\tlen=" << arr_sizes2[i][0] << " start=" << arr_sizes2[i][1] << " end=" << arr_sizes2[i][2] << "\n";
    
        return 0;
    }
    

    在msvc上给出以下结果:

    one,two,three,four,five
    
    one     len=3 start=0 end=3
    two     len=3 start=4 end=7
    thr     len=3 start=8 end=11
    e,f     len=3 start=12 end=15
    ur,     len=3 start=16 end=19
    
    one     len=3 start=0 end=3
    two     len=3 start=4 end=7
    three   len=5 start=8 end=13
    four    len=4 start=14 end=18
    five    len=4 start=19 end=18446744073709551615
    

    Here 是指向更新的完整代码的链接。

    1 回复  |  直到 6 年前
        1
  •  0
  •   Timo    6 年前

    它实际上是一个编译器错误。我不知道是什么导致了这个错误,但它在里面 std::string_view::find_first_of . 奇怪的是,这个bug只发生在常量计算期间(编译时)。据我所知,这个函数的运行时行为与预期的一样。

    以下是 split :

    template <std::size_t N>
    constexpr std::array<std::string_view, N> split(std::string_view str)
    {
        std::array<std::string_view, N> arr{};
        std::size_t start = 0, end = 0;
    
        for (std::size_t i = 0; i < N && end != std::string_view::npos; i++)
        {
            end = std::string_view::npos;
            for (std::size_t j = start; j < str.length(); j++)
            {
                if (str[j] == ',')
                {
                    end = j;
                    break;
                }
            }
    
            arr[i] = str.substr(start, end - start);        
            start = end + 1;
        }
    
        return arr;
    }
    

    编辑

    与gcc和clang相比,msvc在计算常量表达式方面非常糟糕。我发现了很多其他的情况 std::string_view )这也无法编译。