Concat two `const char` string literals

后端 未结 5 2127
北荒
北荒 2020-11-28 12:26

Is it possible to concat two string literals using a constexpr? Or put differently, can one eliminate macros in code like:

#define nl(str) str \         


        
相关标签:
5条回答
  • 2020-11-28 12:36

    A little bit of constexpr, sprinkled with some TMP and a topping of indices gives me this:

    #include <array>
    
    template<unsigned... Is> struct seq{};
    template<unsigned N, unsigned... Is>
    struct gen_seq : gen_seq<N-1, N-1, Is...>{};
    template<unsigned... Is>
    struct gen_seq<0, Is...> : seq<Is...>{};
    
    template<unsigned N1, unsigned... I1, unsigned N2, unsigned... I2>
    constexpr std::array<char const, N1+N2-1> concat(char const (&a1)[N1], char const (&a2)[N2], seq<I1...>, seq<I2...>){
      return {{ a1[I1]..., a2[I2]... }};
    }
    
    template<unsigned N1, unsigned N2>
    constexpr std::array<char const, N1+N2-1> concat(char const (&a1)[N1], char const (&a2)[N2]){
      return concat(a1, a2, gen_seq<N1-1>{}, gen_seq<N2>{});
    }
    

    Live example.

    I'd flesh this out some more, but I have to get going and wanted to drop it off before that. You should be able to work from that.

    0 讨论(0)
  • 2020-11-28 12:36
    • You cannot return a (plain) array from a function.
    • You cannot create a new const char[n] inside a constexpr (§7.1.5/3 dcl.constexpr).
    • An address constant expression must refer to an object of static storage duration (§5.19/3 expr.const) - this disallows some tricks with objects of types having a constexpr ctor assembling the array for concatenation and your constexpr fct just converting it to a ptr.
    • The arguments passed to a constexpr are not considered to be compile-time constants so you can use the fct at runtime, too - this disallows some tricks with template metaprogramming.
    • You cannot get the single char's of a string literal passed to a function as template arguments - this disallows some other template metaprogramming tricks.

    So (as far as I know), you cannot get a constexpr that is returning a char const* of a newly constructed string or a char const[n]. Note most of these restrictions don't hold for an std::array as pointed out by Xeo.

    And even if you could return some char const*, a return value is not a literal, and only adjacent string literals are concatenated. This happens in translation phase 6 (§2.2), which I would still call a preprocessing phase. Constexpr are evaluated later (ref?). (f(x) f(y) where f is a function is a syntax error afaik)

    But you can return from your constexpr fct an object of some other type (with a constexpr ctor or that is an aggregate) that contains both strings and can be inserted/printed into an basic_ostream.


    Edit: here's the example. It's quite a bit long o.O Note you can streamline this in order just to get an additional "\n" add the end of a string. (This is more a generic approach I just wrote down from memory.)

    Edit2: Actually, you cannot really streamline it. Creating the arr data member as an "array of const char_type" with the '\n' included (instead of an array of string literals) uses some fancy variadic template code that's actually a bit longer (but it works, see Xeo's answer).

    Note: as ct_string_vector (the name's not good) stores pointers, it should be used only with strings of static storage duration (such as literals or global variables). The advantage is that a string does not have to be copied & expanded by template mechanisms. If you use a constexpr to store the result (like in the example main), you compiler should complain if the passed parameters are not of static storage duration.

    #include <cstddef>
    #include <iostream>
    #include <iterator>
    
    template < typename T_Char, std::size_t t_len >
    struct ct_string_vector
    {
        using char_type = T_Char;
        using stringl_type = char_type const*;
    
    private:
        stringl_type arr[t_len];
    
    public:
        template < typename... TP >
        constexpr ct_string_vector(TP... pp)
            : arr{pp...}
        {}
    
        constexpr std::size_t length()
        {  return t_len;  }
    
        template < typename T_Traits >
        friend
        std::basic_ostream < char_type, T_Traits >&
        operator <<(std::basic_ostream < char_type, T_Traits >& o,
            ct_string_vector const& p)
        {
            std::copy( std::begin(p.arr), std::end(p.arr),
                std::ostream_iterator<stringl_type>(o) );
            return o;
        }
    };
    
    template < typename T_String >
    using get_char_type =
        typename std::remove_const < 
        typename std::remove_pointer <
        typename std::remove_reference <
        typename std::remove_extent <
            T_String
        > :: type > :: type > :: type > :: type;
    
    template < typename T_String, typename... TP >
    constexpr
    ct_string_vector < get_char_type<T_String>, 1+sizeof...(TP) >
    make_ct_string_vector( T_String p, TP... pp )
    {
        // can add an "\n" at the end of the {...}
        // but then have to change to 2+sizeof above
        return {p, pp...};
    }
    
    // better version of adding an '\n':
    template < typename T_String, typename... TP >
    constexpr auto
    add_newline( T_String p, TP... pp )
    -> decltype( make_ct_string_vector(p, pp..., "\n") )
    {
        return make_ct_string_vector(p, pp..., "\n");
    }
    
    int main()
    {
        // ??? (still confused about requirements of constant init, sry)
        static constexpr auto assembled = make_ct_string_vector("hello ", "world");
        enum{ dummy = assembled.length() }; // enforce compile-time evaluation
        std::cout << assembled << std::endl;
        std::cout << add_newline("first line") << "second line" << std::endl;
    }
    
    0 讨论(0)
  • 2020-11-28 12:42

    Nope, for constexpr you need a legal function in the first place, and functions can't do pasting etc. of string literal arguments.

    If you think about the equivalent expression in a regular function, it would be allocating memory and concatenating the strings - definitely not amenable to constexpr.

    0 讨论(0)
  • 2020-11-28 12:55
    1. Yes, it is entirely possible to create compile-time constant strings, and manipulate them with constexpr functions and even operators. However,

    2. The compiler is not required to perform constant initialization of any object other than static- and thread-duration objects. In particular, temporary objects (which are not variables, and have something less than automatic storage duration) are not required to be constant initialized, and as far as I know no compiler does that for arrays. See 3.6.2/2-3, which define constant initialization, and 6.7.4 for some more wording with respect to block-level static duration variables. Neither of these apply to temporaries, whose lifetime is defined in 12.2/3 and following.

    So you could achieve the desired compile-time concatenation with:

    static const auto conc = <some clever constexpr thingy>;
    std::cout << conc;
    

    but you can't make it work with:

    std::cout <<  <some clever constexpr thingy>;
    

    Update:

    But you can make it work with:

    std::cout << *[]()-> const {
                 static constexpr auto s = /* constexpr call */;
                 return &s;}()
              << " some more text";
    

    But the boilerplate punctuation is way too ugly to make it any more than an interesting little hack.


    (Disclaimer: IANALL, although sometimes I like to play one on the internet. So there might be some dusty corners of the standard which contradicts the above.)

    (Despite the disclaimer, and pushed by @DyP, I added some more language-lawyerly citations.)

    0 讨论(0)
  • 2020-11-28 12:59

    At first glance, C++11 user-defined string literals appear to be a much simpler approach. (If, for example, you're looking for a way to globally enable and disable newline injection at compile time)

    0 讨论(0)
提交回复
热议问题