【问题标题】:Create a constexpr C string from concatenation of a some string literal and an int template parameter从某个字符串文字和一个 int 模板参数的串联创建一个 constexpr C 字符串
【发布时间】:2018-11-07 12:39:46
【问题描述】:

我有一个带有 int 模板参数的类。在某些情况下,我希望它输出错误消息。此消息应该是一些固定文本和模板参数的串联字符串。出于性能原因,我想避免在每次发生错误时在运行时构建此字符串,理论上,字符串文字和模板参数在编译时都是已知的。所以我正在寻找一种可能性,将其声明为 constexpr。

代码示例:

template<int size>
class MyClass
{
    void onError()
    {
        // obviously won't work but expressing the concatenation like
        // it would be done with a std::string for clarification 
        constexpr char errMsg[] = "Error in MyClass of size " + std::to_string (size) + ": Detailed error description\n";

        outputErrorMessage (errMsg);
    }
}

【问题讨论】:

  • 在您的应用程序中输出错误消息真的是一个热门路径吗?
  • 好点。也许不是那么热,但是错误是从实时处理循环中抛出的,所以部分是的。但即使不是,我也想知道答案,只是为了学习
  • 希望编译器开发者尽快实现P0732,这样可以通过模板解决。
  • 您的字符串“MyClass 中的错误...”是否不变?我的意思是,您是否也打算使用另一个字符串,或者只是这个?因为如果你只有这个,这个问题已经很容易解决了。
  • 不确定我是否正确理解了你的问题@geza,但这个类的字符串总是相同的,只有模板参数和字符串中包含的数字可能会改变

标签: c++ string constexpr


【解决方案1】:

使用static const 只允许计算一次(但在运行时):

template<int size>
class MyClass
{
    void onError()
    {
        static const std::string = "Error in MyClass of size "
                                  + std::to_string(size)
                                  + ": Detailed error description\n";

        outputErrorMessage(errMsg);
    }
};

如果你真的想在编译时得到那个字符串,你可以使用std::array,类似:

template <std::size_t N>
constexpr std::size_t count_digit() {
    if (N == 0) {
        return 1;   
    }
    std::size_t res = 0;
    for (int i = N; i; i /= 10) {
        ++res;
    }
    return res;
}

template <std::size_t N>
constexpr auto to_char_array()
{
    constexpr auto digit_count = count_digit<N>();
    std::array<char, digit_count> res{};

    auto n = N;
    for (std::size_t i = 0; i != digit_count; ++i) {
        res[digit_count - 1 - i] = static_cast<char>('0' + n % 10);
        n /= 10;
    }
    return res;
}

template <std::size_t N>
constexpr std::array<char, N - 1> to_array(const char (&a)[N])
{
    std::array<char, N - 1> res{};

    for (std::size_t i = 0; i != N - 1; ++i) {
        res[i] = a[i];
    }
    return res;
}

template <std::size_t ...Ns>
constexpr std::array<char, (Ns + ...)> concat(const std::array<char, Ns>&... as)
{
    std::array<char, (Ns + ...)> res{};
    std::size_t i = 0;
    auto l = [&](const auto& a) { for (auto c : a) {res[i++] = c;} };

    (l(as), ...);
    return res;
}

最后:

template<int size>
class MyClass
{
public:
    void onError()
    {
        constexpr auto errMsg = concat(to_array("Error in MyClass of size "),
                                  to_char_array<size>(),
                                  to_array(": Detailed error description\n"),
                                  std::array<char, 1>{{0}});

        std::cout << errMsg.data();
    }
};

Demo

【讨论】:

    【解决方案2】:

    这是我的解决方案。在godbolt上测试:

    #include <string_view>
    #include <array>
    #include <algorithm>
    
    void outputErrorMessage(std::string_view s);
    
    template<int N> struct cint
    {
        constexpr int value() const { return N; }
    };
    
    struct concat_op {};
    
    template<std::size_t N>
    struct fixed_string
    {
        constexpr static std::size_t length() { return N; }
        constexpr static std::size_t capacity() { return N + 1; }
    
        template<std::size_t L, std::size_t R>
        constexpr fixed_string(concat_op, fixed_string<L> l, fixed_string<R> r)
        : fixed_string()
        {
            static_assert(L + R == N);   
            overwrite(0, l.data(), L);
            overwrite(L, r.data(), R);     
        }
    
        constexpr fixed_string()
        : buffer_ { 0 }
        {
        }
    
        constexpr fixed_string(const char (&source)[N + 1])
        : fixed_string()
        {
            do_copy(source, buffer_.data());
        }
    
        static constexpr void do_copy(const char (&source)[N + 1], char* dest)
        {
            for(std::size_t i = 0 ; i < capacity() ; ++i)
                dest[i] = source[i];
        }
    
        constexpr const char* data() const
        {
            return buffer_.data();
        }
    
        constexpr const char* data()
        {
            return buffer_.data();
        }
    
        constexpr void overwrite(std::size_t where, const char* source, std::size_t len)
        {
            auto dest = buffer_.data() + where;
            while(len--)
                *dest++ = *source++;
        }
    
        operator std::string_view() const
        {
            return { buffer_.data(), N };
        }
    
        std::array<char, capacity()> buffer_;
    };
    
    template<std::size_t N> fixed_string(const char (&)[N]) -> fixed_string<N - 1>;
    
    template<std::size_t L, std::size_t R>
    constexpr auto operator+(fixed_string<L> l, fixed_string<R> r) -> fixed_string<L + R>
    {
        auto result = fixed_string<L + R>(concat_op(), l , r);
        return result;
    };
    
    template<int N> 
    constexpr auto to_string()
    {
        auto log10 = []
        {
            if constexpr (N < 10)
                return 1;
            else if constexpr(N < 100)
                return 2;
            else if constexpr(N < 1000)
                return 3;
            else 
                return 4; 
                // etc
        };
    
        constexpr auto len = log10();
    
        auto result = fixed_string<len>();
    
        auto pow10 = [](int n, int x)
        {
            if (x == 0)
                return 1;
            else while(x--)
                n *= 10;
            return n;
        };
    
        auto to_char = [](int n)
        {
            return '0' + char(n);
        };
    
        int n = N;
        for (int i = 0 ; i < len ; ++i)
        {
            auto pow = pow10(10, i);
            auto digit = to_char(n % 10);
            if (n == 0 && i != 0) digit = ' ';
            result.buffer_[len - i - 1] = digit;
            n /= 10;
        }
    
        return result;
    }
    
    template<int size>
    struct MyClass
    {
        void onError()
        {
            // obviously won't work but expressing the concatenation like
            // it would be done with a std::string for clarification 
            static const auto errMsg = fixed_string("Error in MyClass of size ") + to_string<size>() + fixed_string(": Detailed error description\n");
    
    
            outputErrorMessage (errMsg);
        }
    };
    
    int main()
    {
        auto x = MyClass<10>();
        x.onError();
    }
    

    结果如下:

    main:
      sub rsp, 8
      mov edi, 56
      mov esi, OFFSET FLAT:MyClass<10>::onError()::errMsg
      call outputErrorMessage(std::basic_string_view<char, std::char_traits<char> >)
      xor eax, eax
      add rsp, 8
      ret
    

    https://godbolt.org/z/LTgn4F

    更新:

    不需要调用 pow10。这是可以删除的死代码。

    【讨论】:

    • 你可以在 constexpr 中计算 log10:constexpr long long log(long long n, long long base = 2) { return ( (n &lt; base) ? 1 : 1 + log(n / base, base)); } 然后constexpr auto log10 = log(n, 10);
    • @KamilCuk 不错的解决方案。
    • @jarod42 回滚到静态 const - 在这种情况下,gcc 在常量折叠方面比 constexpr 做得更好。
    • @RichardHodges:最初,它是为了删除过时的评论 :) 是 static 使事情变得不同 BTW,而不是 constconstexpr... 肯定是因为 @987654329 @。我读了 constexpr 以表明它确实是在编译时计算的。
    • @Jarod42 值得看看 Godbolt 上的输出。 clang 总是比 gcc 更好地处理编译时字符串操作。
    【解决方案3】:

    很遗憾,您的选择有限。 C++ 不允许将字符串文字用于模板参数,即使这样做,文字连接也会在模板进入之前在预处理器中发生。您将需要一些可怕的逐个字符的数组定义和一些手动的 int 到 char 的转换。太可怕了,我无法让自己尝试,而且太可怕了,老实说,我建议不要打扰。我会在运行时生成它,尽管只生成一次(你可以使 errMsg 成为一个函数-static std::string 至少)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-07-24
      • 2021-03-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-11-08
      • 1970-01-01
      相关资源
      最近更新 更多