การประกาศสตริงเวลาคอมไพล์ใน C ++ อย่างสะดวกสบาย


138

ความสามารถในการสร้างและจัดการสตริงระหว่างเวลาคอมไพล์ใน C ++ มีแอพพลิเคชั่นที่มีประโยชน์มากมาย แม้ว่าจะสามารถสร้างสตริงเวลาคอมไพล์ใน C ++ ได้ แต่กระบวนการนี้ยุ่งยากมากเนื่องจากต้องประกาศสตริงเป็นลำดับตัวแปรของอักขระเช่น

using str = sequence<'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!'>;

การดำเนินการเช่นการต่อสายอักขระการแยกสตริงย่อยและอื่น ๆ อีกมากมายสามารถนำไปใช้งานได้อย่างง่ายดายเป็นการดำเนินการกับลำดับของอักขระ เป็นไปได้ไหมที่จะประกาศสตริงเวลาคอมไพล์สะดวกกว่า ถ้าไม่มีมีข้อเสนอในผลงานที่อนุญาตให้มีการประกาศสตริงเวลาคอมไพล์ได้อย่างสะดวกหรือไม่?

เหตุใดแนวทางที่มีอยู่จึงล้มเหลว

ตามหลักการแล้วเราต้องการที่จะสามารถประกาศสตริงเวลาคอมไพล์ได้ดังนี้:

// Approach 1
using str1 = sequence<"Hello, world!">;

หรือใช้ตัวอักษรที่ผู้ใช้กำหนดเอง

// Approach 2
constexpr auto str2 = "Hello, world!"_s;

ที่decltype(str2)จะมีตัวconstexprสร้าง แนวทางที่ 1 เป็นไปได้ที่จะใช้งานได้ง่ายขึ้นโดยใช้ประโยชน์จากความจริงที่ว่าคุณสามารถทำสิ่งต่อไปนี้:

template <unsigned Size, const char Array[Size]>
struct foo;

อย่างไรก็ตามอาร์เรย์จะต้องมีการเชื่อมโยงภายนอกดังนั้นเพื่อให้แนวทางที่ 1 ทำงานได้เราจะต้องเขียนสิ่งนี้:

/* Implementation of array to sequence goes here. */

constexpr const char str[] = "Hello, world!";

int main()
{
    using s = string<13, str>;
    return 0;
}

ไม่จำเป็นต้องพูดสิ่งนี้ไม่สะดวกมาก แนวทางที่ 2 ไม่สามารถนำไปใช้ได้จริง ถ้าเราจะประกาศ a ( constexpr) ตัวดำเนินการตามตัวอักษรเราจะระบุประเภทการส่งคืนอย่างไร เนื่องจากเราต้องการตัวดำเนินการในการส่งคืนลำดับconst char*ตัวแปรของอักขระดังนั้นเราจึงต้องใช้พารามิเตอร์เพื่อระบุประเภทการส่งคืน

constexpr auto
operator"" _s(const char* s, size_t n) -> /* Some metafunction using `s` */

ซึ่งส่งผลให้เกิดข้อผิดพลาดในการคอมไพล์เนื่องจากsไม่ใช่ไฟล์constexpr. การพยายามแก้ไขปัญหานี้โดยการทำสิ่งต่อไปนี้ไม่ได้ช่วยอะไรมาก

template <char... Ts>
constexpr sequence<Ts...> operator"" _s() { return {}; }

มาตรฐานกำหนดว่าแบบฟอร์มตัวดำเนินการตามตัวอักษรเฉพาะนี้สงวนไว้สำหรับชนิดจำนวนเต็มและทศนิยม ในขณะที่123_sจะทำงานabc_sจะไม่ จะเกิดอะไรขึ้นถ้าเราทิ้งตัวอักษรที่ผู้ใช้กำหนดไว้ทั้งหมดและใช้constexprฟังก์ชันปกติ?

template <unsigned Size>
constexpr auto
string(const char (&array)[Size]) -> /* Some metafunction using `array` */

ก่อนหน้านี้เราพบปัญหาว่าอาร์เรย์ซึ่งตอนนี้เป็นพารามิเตอร์ของconstexprฟังก์ชันนั้นไม่ใช่constexprประเภทอีกต่อไป

ฉันเชื่อว่าควรกำหนดมาโครตัวประมวลผลล่วงหน้า C ที่ใช้สตริงและขนาดของสตริงเป็นอาร์กิวเมนต์และส่งคืนลำดับที่ประกอบด้วยอักขระในสตริง (โดยใช้การสร้างสตริงการBOOST_PP_FORห้อยอาร์เรย์และสิ่งที่คล้ายกัน) อย่างไรก็ตามฉันไม่มีเวลา (หรือสนใจมากพอ) ที่จะใช้มาโครดังกล่าว =)


2
Boost มีมาโครที่กำหนดสตริงซึ่งสามารถใช้เป็นนิพจน์คงที่ได้ มันกำหนดคลาสที่มีสมาชิกสตริง คุณตรวจสอบว่า?
Pubby

6
คุณตรวจสอบcpp-next.com/archive/2012/10/…หรือไม่
Evgeny Panasyuk

1
Stack Overflow ไม่ใช่สถานที่ที่เหมาะสมในการถามว่ามีข้อเสนอสำหรับบางสิ่งหรือไม่ สถานที่ที่ดีที่สุดสำหรับการนี้จะเป็นเว็บไซต์ภาษา C ++
Nicol Bolas

1
โดยทั่วไปคุณจะขยายอักขระที่เก็บไว้ในอาร์เรย์ / ptr ลงในชุดพารามิเตอร์ (เช่นเดียวกับ Xeo) แม้ว่าจะไม่แยกออกเป็นอาร์กิวเมนต์แม่แบบที่ไม่ใช่ประเภท แต่คุณสามารถใช้ภายในconstexprฟังก์ชันและเริ่มต้นอาร์เรย์ (ดังนั้น concat, substr ฯลฯ )
dyp

1
@MareInfinitus ในระยะสั้นconstexprสตริงสามารถแยกวิเคราะห์ระหว่างเวลาคอมไพล์เพื่อให้คุณสามารถใช้เส้นทางรหัสที่แตกต่างกันขึ้นอยู่กับผลลัพธ์ โดยพื้นฐานแล้วคุณสามารถสร้าง EDL ภายใน C ++ แอพพลิเคชั่นนั้นค่อนข้างไร้ขีด จำกัด
void-pointer

คำตอบ:


127

ผมยังไม่ได้เห็นอะไรเพื่อให้ตรงกับความสง่างามของสกอตต์ไบโอซิสเต็มส์ได้str_constนำเสนอในc ++ ตอนนี้ 2012 มันต้องการconstexprแม้ว่า

นี่คือวิธีการใช้งานและสิ่งที่สามารถทำได้:

int
main()
{
    constexpr str_const my_string = "Hello, world!";
    static_assert(my_string.size() == 13, "");
    static_assert(my_string[4] == 'o', "");
    constexpr str_const my_other_string = my_string;
    static_assert(my_string == my_other_string, "");
    constexpr str_const world(my_string, 7, 5);
    static_assert(world == "world", "");
//  constexpr char x = world[5]; // Does not compile because index is out of range!
}

มันไม่ได้เจ๋งไปกว่าการตรวจสอบช่วงเวลาคอมไพล์!

ทั้งการใช้งานและการนำไปใช้งานไม่มีมาโคร และไม่มีการ จำกัด ขนาดสตริงเทียม ฉันจะโพสต์การใช้งานที่นี่ แต่ฉันเคารพในลิขสิทธิ์โดยปริยายของ Scott การใช้งานอยู่ในสไลด์เดียวของงานนำเสนอที่เชื่อมโยงกับด้านบน


3
การดำเนินการที่สร้างสตริง constexpr ใหม่ (เช่นการต่อสตริงและการแยกสตริงย่อย) สามารถทำงานร่วมกับแนวทางนี้ได้หรือไม่ บางทีการใช้คลาสสตริง constexpr สองคลาส (แบบหนึ่งขึ้นอยู่กับstr_constและอีกคลาสอิงตามsequence) อาจเป็นไปได้ ผู้ใช้จะใช้str_constเพื่อเริ่มต้นสตริง แต่การดำเนินการในภายหลังที่สร้างสตริงใหม่จะส่งคืนsequenceอ็อบเจ็กต์
void-pointer

5
นี่เป็นรหัสที่ดี อย่างไรก็ตามวิธีนี้ยังคงมีข้อบกพร่องเมื่อเทียบกับสตริงที่ประกาศโดยมีลำดับอักขระเป็นพารามิเตอร์เทมเพลต: str_const เป็นค่าคงที่ไม่ใช่ประเภทจึงป้องกันไม่ให้มีการใช้สำนวน metaprogramming จำนวนมาก
Jean-Bernard Jansen

1
@JBJansen เป็นไปได้โดยไม่มีฟังก์ชันแฮชเพื่อรวบรวมสตริงเป็นประเภทที่สามารถใช้เป็นพารามิเตอร์เทมเพลตได้ แต่ละสายอักขระที่แตกต่างกันให้ประเภท template<char... cs>ความคิดพื้นฐานคือการเปิดสตริงเป็นแพ็คตัวละคร ในทางทฤษฎีคุณสามารถสร้างสิ่งที่ใช้สตริงตามตัวอักษรและรวบรวมเนื้อหาให้เป็นฟังก์ชัน ดูคำตอบโดย dyp ห้องสมุดที่สมบูรณ์มากมองเป็นmetaparse โดยพื้นฐานแล้วคุณสามารถกำหนดการแมปใด ๆ จากสตริงตามตัวอักษรไปยังประเภทและนำไปใช้กับเทคโนโลยีประเภทนี้
Aaron McDaid

1
ฉันไม่แบ่งปันความกระตือรือร้น ... ใช้ไม่ได้กับ metafunctions ของเทมเพลต - น่ารำคาญมากเนื่องจากการประนีประนอมโง่ ๆ ที่ฟังก์ชัน constexpr จะสามารถเรียกใช้ได้ที่รันไทม์ - ไม่มีการต่อกันที่แท้จริงต้องใช้คำจำกัดความของอาร์เรย์ char (น่าเกลียดในส่วนหัว) - แม้ว่าสิ่งนี้ เป็นความจริงของโซลูชันที่ไม่ใช้มาโครส่วนใหญ่เนื่องจากการประนีประนอม constexpr ดังกล่าวข้างต้นและการตรวจสอบช่วงไม่ได้ทำให้ฉันประทับใจมากนักเพราะแม้แต่ constexpr const char * ที่ต่ำก็มีเช่นนั้น ฉันรีดสตริงแพ็คพารามิเตอร์ของตัวเองซึ่งสามารถสร้างจากลิเทอรัล (โดยใช้เมตาฟังก์ชัน) ในราคาของนิยามอาร์เรย์
Arne Vogel

2
@ user975326: ฉันเพิ่งตรวจสอบการใช้งานสิ่งนี้และดูเหมือนว่าฉันจะเพิ่มไฟล์constexpr operator==. ขออภัย. การนำเสนอของ Scott ควรช่วยให้คุณเริ่มทำสิ่งนี้ได้ มันง่ายกว่าใน C ++ 14 มากกว่าใน C ++ 11 ฉันไม่แม้แต่จะลองใช้ C ++ 11 ดูการconstexprพูดคุยล่าสุดของ Scott ได้ที่นี่: youtube.com/user/CppCon
Howard Hinnant

41

ฉันเชื่อว่าควรกำหนดมาโครตัวประมวลผลล่วงหน้า C ที่ใช้สตริงและขนาดของสตริงเป็นอาร์กิวเมนต์และส่งคืนลำดับที่ประกอบด้วยอักขระในสตริง (โดยใช้ BOOST_PP_FOR, การทำให้สตริง, ตัวห้อยอาร์เรย์และอื่น ๆ ) อย่างไรก็ตามฉันไม่มีเวลา (หรือสนใจมากพอ) ที่จะใช้มาโครดังกล่าว

เป็นไปได้ที่จะใช้สิ่งนี้โดยไม่ต้องอาศัยการเพิ่มโดยใช้มาโครที่เรียบง่ายและคุณสมบัติบางอย่างของ C ++ 11:

  1. lambdas หลากหลาย
  2. เทมเพลต
  3. นิพจน์ทั่วไปคงที่
  4. ตัวเริ่มต้นสมาชิกข้อมูลแบบไม่คงที่
  5. การเริ่มต้นสม่ำเสมอ

(สองข้อหลังไม่จำเป็นอย่างเคร่งครัดที่นี่)

  1. เราจำเป็นต้องสามารถสร้างอินสแตนซ์เทมเพลตแบบแปรผันด้วยตัวบ่งชี้ที่ผู้ใช้ระบุตั้งแต่ 0 ถึง N ซึ่งเป็นเครื่องมือที่มีประโยชน์เช่นในการขยายทูเพิลให้เป็นอาร์กิวเมนต์ของฟังก์ชันเทมเพลตตัวแปร (ดูคำถาม: ฉันจะขยายทูเพิลเป็นอาร์กิวเมนต์ของฟังก์ชันเทมเพลตตัวแปรได้อย่างไร
    " การคลาย "ทูเพิลเพื่อเรียกตัวชี้ฟังก์ชันที่ตรงกัน )

    namespace  variadic_toolbox
    {
        template<unsigned  count, 
            template<unsigned...> class  meta_functor, unsigned...  indices>
        struct  apply_range
        {
            typedef  typename apply_range<count-1, meta_functor, count-1, indices...>::result  result;
        };
    
        template<template<unsigned...> class  meta_functor, unsigned...  indices>
        struct  apply_range<0, meta_functor, indices...>
        {
            typedef  typename meta_functor<indices...>::result  result;
        };
    }
    
  2. จากนั้นกำหนดแม่แบบตัวแปรที่เรียกว่าสตริงด้วยพารามิเตอร์ที่ไม่ใช่ประเภท char:

    namespace  compile_time
    {
        template<char...  str>
        struct  string
        {
            static  constexpr  const char  chars[sizeof...(str)+1] = {str..., '\0'};
        };
    
        template<char...  str>
        constexpr  const char  string<str...>::chars[sizeof...(str)+1];
    }
    
  3. ตอนนี้เป็นส่วนที่น่าสนใจที่สุด - เพื่อส่งผ่านตัวอักษรไปยังเทมเพลตสตริง:

    namespace  compile_time
    {
        template<typename  lambda_str_type>
        struct  string_builder
        {
            template<unsigned... indices>
            struct  produce
            {
                typedef  string<lambda_str_type{}.chars[indices]...>  result;
            };
        };
    }
    
    #define  CSTRING(string_literal)                                                        \
        []{                                                                                 \
            struct  constexpr_string_type { const char * chars = string_literal; };         \
            return  variadic_toolbox::apply_range<sizeof(string_literal)-1,                 \
                compile_time::string_builder<constexpr_string_type>::produce>::result{};    \
        }()
    

การสาธิตการเรียงต่อกันอย่างง่ายแสดงการใช้งาน:

    namespace  compile_time
    {
        template<char...  str0, char...  str1>
        string<str0..., str1...>  operator*(string<str0...>, string<str1...>)
        {
            return  {};
        }
    }

    int main()
    {
        auto  str0 = CSTRING("hello");
        auto  str1 = CSTRING(" world");

        std::cout << "runtime concat: " <<  str_hello.chars  << str_world.chars  << "\n <=> \n";
        std::cout << "compile concat: " <<  (str_hello * str_world).chars  <<  std::endl;
    }

https://ideone.com/8Ft2xu


1
มันง่ายมากจนฉันยังไม่อยากจะเชื่อเลยว่ามันได้ผล +1! สิ่งหนึ่ง: คุณไม่ควรใช้ size_t แทนการไม่ได้ลงนาม?
kirbyfan64sos

1
แล้วใช้operator+แทนoperator*อะไร? (str_hello + str_world)
Remy Lebeau

ฉันชอบโซลูชันนี้มากกว่าวิธี str_const ของ Scott Schurr ที่เป็นที่นิยมเนื่องจากวิธีนี้ทำให้มั่นใจได้ว่าข้อมูลพื้นฐานเป็น constexpr วิธีการของ Schurr ให้ฉันสร้าง str_const ที่รันไทม์ด้วยตัวแปรสแต็ก char []; ฉันไม่สามารถส่งคืน str_const จากฟังก์ชันหรือส่งต่อไปยังเธรดอื่นได้อย่างปลอดภัย
Glenn

ลิงค์ตายแล้ว ... ทุกคนสามารถโพสต์ใหม่ได้หรือไม่? @ เกลนน์?
einpoklum

คุณควรเพิ่มวงเล็บปีกกาพิเศษรอบแลมด้าในCSTRINGมาโครของคุณ มิฉะนั้นคุณจะไม่สามารถสร้างการCSTRINGโทรภายในไปยัง[]โอเปอเรเตอร์ได้เนื่องจากคู่[[ถูกสงวนไว้สำหรับแอตทริบิวต์
florestan

23

แก้ไข: ตามที่ Howard Hinnant (และฉันค่อนข้างแสดงความคิดเห็นต่อ OP) คุณอาจไม่จำเป็นต้องใช้ประเภทที่มีอักขระทุกตัวของสตริงเป็นอาร์กิวเมนต์เทมเพลตเดียว หากคุณต้องการสิ่งนี้มีโซลูชันที่ไม่มีมาโครอยู่ด้านล่าง

มีเคล็ดลับที่ฉันพบขณะพยายามทำงานกับสตริงในเวลาคอมไพล์ ต้องมีการแนะนำประเภทอื่นนอกเหนือจาก "สตริงเทมเพลต" แต่ภายในฟังก์ชันคุณสามารถ จำกัด ขอบเขตของประเภทนี้ได้

ไม่ได้ใช้มาโคร แต่เป็นคุณลักษณะบางอย่างของ C ++ 11

#include <iostream>

// helper function
constexpr unsigned c_strlen( char const* str, unsigned count = 0 )
{
    return ('\0' == str[0]) ? count : c_strlen(str+1, count+1);
}

// destination "template string" type
template < char... chars >
struct exploded_string
{
    static void print()
    {
        char const str[] = { chars... };
        std::cout.write(str, sizeof(str));
    }
};

// struct to explode a `char const*` to an `exploded_string` type
template < typename StrProvider, unsigned len, char... chars  >
struct explode_impl
{
    using result =
        typename explode_impl < StrProvider, len-1,
                                StrProvider::str()[len-1],
                                chars... > :: result;
};

    // recursion end
    template < typename StrProvider, char... chars >
    struct explode_impl < StrProvider, 0, chars... >
    {
         using result = exploded_string < chars... >;
    };

// syntactical sugar
template < typename StrProvider >
using explode =
    typename explode_impl < StrProvider,
                            c_strlen(StrProvider::str()) > :: result;


int main()
{
    // the trick is to introduce a type which provides the string, rather than
    // storing the string itself
    struct my_str_provider
    {
        constexpr static char const* str() { return "hello world"; }
    };
    
    auto my_str = explode < my_str_provider >{};    // as a variable
    using My_Str = explode < my_str_provider >;    // as a type
    
    my_str.print();
}
 

1
pair<int,pair<char,double>>ฉันได้ใช้เวลาเพียงแค่วันหยุดสุดสัปดาห์เป็นอิสระการพัฒนาชิ้นส่วนของรหัสที่คล้ายกันและทำให้ระบบพื้นฐานมากที่จะสตริงประเภทแยกเช่น ฉันภูมิใจในตัวเองจากนั้นก็ค้นพบคำตอบนี้และห้องสมุดmetaparseวันนี้! ฉันควรค้นหา SO ให้ละเอียดมากขึ้นก่อนที่จะเริ่มโครงการโง่ ๆ เช่นนี้ :-) ฉันเดาว่าตามทฤษฎีแล้วคอมไพเลอร์ C ++ เต็มสามารถสร้างขึ้นจากเทคโนโลยีประเภทนี้ อะไรคือสิ่งที่บ้าคลั่งที่สุดที่ถูกสร้างขึ้นด้วยสิ่งนี้?
Aaron McDaid

ฉันไม่รู้ ฉันไม่เคยใช้เทคนิคเหล่านี้ในโครงการจริงดังนั้นฉันจึงไม่ได้ติดตามแนวทางนี้ แต่ผมคิดว่าผมจำได้ว่าการเปลี่ยนแปลงเล็กน้อยของท้องถิ่นชนิดซึ่งเป็นเคล็ดลับที่สะดวกมากขึ้นเล็กน้อย .. char[]อาจจะเป็นแบบคงที่ในท้องถิ่น
dyp

คุณหมายถึงmy_str.print();แทนstr.print();?
ไมค์

มี C ++ 14 เวอร์ชันที่สั้นกว่าเล็กน้อยหรือไม่?
ไมค์

1
แทนที่จะใช้เครื่องพิมพ์แบบวนซ้ำฉันคิดว่าตัวเลือกที่ง่ายกว่าคือการทำchar str[] = {ttc...}; std::cout << str << std::endl;
Ajay Brahmakshatriya

11

หากคุณไม่ต้องการใช้โซลูชัน Boostคุณสามารถสร้างมาโครง่ายๆที่จะทำสิ่งที่คล้ายกัน:

#define MACRO_GET_1(str, i) \
    (sizeof(str) > (i) ? str[(i)] : 0)

#define MACRO_GET_4(str, i) \
    MACRO_GET_1(str, i+0),  \
    MACRO_GET_1(str, i+1),  \
    MACRO_GET_1(str, i+2),  \
    MACRO_GET_1(str, i+3)

#define MACRO_GET_16(str, i) \
    MACRO_GET_4(str, i+0),   \
    MACRO_GET_4(str, i+4),   \
    MACRO_GET_4(str, i+8),   \
    MACRO_GET_4(str, i+12)

#define MACRO_GET_64(str, i) \
    MACRO_GET_16(str, i+0),  \
    MACRO_GET_16(str, i+16), \
    MACRO_GET_16(str, i+32), \
    MACRO_GET_16(str, i+48)

#define MACRO_GET_STR(str) MACRO_GET_64(str, 0), 0 //guard for longer strings

using seq = sequence<MACRO_GET_STR("Hello world!")>;

ปัญหาเดียวคือขนาดคงที่ 64 ตัวอักษร (บวกศูนย์เพิ่มเติม) แต่สามารถเปลี่ยนแปลงได้ง่ายขึ้นอยู่กับความต้องการของคุณ


1
ฉันชอบวิธีแก้ปัญหานี้มาก มันง่ายมากและทำงานได้อย่างหรูหรา เป็นไปได้หรือไม่ที่จะแก้ไขมาโครเพื่อไม่ให้ไม่มีการต่อท้ายsizeof(str) > i(แทนที่จะต่อท้าย0,โทเค็นพิเศษ) เป็นเรื่องง่ายที่จะกำหนดtrimเมตาฟังก์ชั่นที่จะทำสิ่งนี้หลังจากที่เรียกมาโครไปแล้ว แต่จะเป็นการดีหากสามารถปรับเปลี่ยนมาโครเองได้
void-pointer

เป็นไปไม่ได้เพราะ parser sizeof(str)ไม่เข้าใจ เป็นไปได้ที่จะเพิ่มขนาดสตริงด้วยตนเองเช่นMACRO_GET_STR(6, "Hello")นี้ แต่ต้องใช้แมโคร Boost ในการทำงานเนื่องจากการเขียนด้วยตนเองต้องใช้โค้ดมากกว่า 100 เท่า (คุณต้องใช้สิ่งง่ายๆเช่น1+1)
Yankes

6

ฉันเชื่อว่าควรกำหนดมาโครตัวประมวลผลล่วงหน้า C ที่ใช้สตริงและขนาดของสตริงเป็นอาร์กิวเมนต์และส่งคืนลำดับที่ประกอบด้วยอักขระในสตริง (โดยใช้ BOOST_PP_FOR, การทำให้สตริง, ตัวห้อยอาร์เรย์และอื่น ๆ )

มีบทความ: การใช้สตริงในเมตาโปรแกรมแม่แบบ C ++โดย Abel Sinkovics และ Dave Abrahams

มีการปรับปรุงแนวคิดของคุณในการใช้มาโคร + BOOST_PP_REPEAT - ไม่จำเป็นต้องส่งขนาดที่ชัดเจนไปยังมาโคร กล่าวโดยย่อคือขึ้นอยู่กับขีด จำกัด บนคงที่สำหรับขนาดสตริงและ "การป้องกันการบุกรุกสตริง"

template <int N>
constexpr char at(char const(&s)[N], int i)
{
    return i >= N ? '\0' : s[i];
}

บวกเงื่อนไขเพิ่ม :: MPL :: push_back


ฉันเปลี่ยนคำตอบที่ยอมรับเป็นโซลูชันของ Yankes เนื่องจากมันช่วยแก้ปัญหาเฉพาะนี้ได้และทำได้อย่างสวยงามโดยไม่ต้องใช้ constexpr หรือรหัสก่อนตัวประมวลผลที่ซับซ้อน

หากคุณยอมรับค่าศูนย์ต่อท้ายการวนซ้ำมาโครที่เขียนด้วยมือการทำซ้ำ2xของสตริงในมาโครขยายและไม่มี Boost - ฉันเห็นด้วย - จะดีกว่า แม้ว่าการเพิ่มจะเป็นเพียงสามบรรทัด:

การสาธิตสด

#include <boost/preprocessor/repetition/repeat.hpp>
#define GET_STR_AUX(_, i, str) (sizeof(str) > (i) ? str[(i)] : 0),
#define GET_STR(str) BOOST_PP_REPEAT(64,GET_STR_AUX,str) 0

ตอนแรกฉันเปลี่ยนวิธีแก้ปัญหาเป็น Yankes 'เนื่องจากเขาให้ตัวอย่างการทำงานแรกที่นี่ ณ จุดนี้มีแนวคิดการแข่งขันที่ดีมากมาย มันเป็นความผิดพลาดของฉันที่เลือกคำตอบตั้งแต่เนิ่นๆ ตอนนี้ฉันจะตั้งข้อสังเกตว่าคำถามนี้ยังไม่มีคำตอบและรอจนกว่าจะมีเวลาลองใช้แนวคิดที่ทุกคนโพสต์ไว้ที่นี่ มีข้อมูลที่เป็นประโยชน์มากมายในคำตอบที่ผู้คนให้ไว้ที่นี่ ...
void-pointer

ฉันเห็นด้วย - เช่นฉันชอบตัวอย่างของ Howard Hinnant
Evgeny Panasyuk

5

เพื่อนร่วมงานคนหนึ่งท้าทายให้ฉันต่อสายอักขระในหน่วยความจำตามเวลาคอมไพล์ รวมถึงการสร้างอินสแตนซ์สตริงแต่ละรายการในเวลาคอมไพล์ด้วย รายชื่อรหัสเต็มอยู่ที่นี่:

//Arrange strings contiguously in memory at compile-time from string literals.
//All free functions prefixed with "my" to faciliate grepping the symbol tree
//(none of them should show up).

#include <iostream>

using std::size_t;

//wrapper for const char* to "allocate" space for it at compile-time
template<size_t N>
struct String {
    //C arrays can only be initialised with a comma-delimited list
    //of values in curly braces. Good thing the compiler expands
    //parameter packs into comma-delimited lists. Now we just have
    //to get a parameter pack of char into the constructor.
    template<typename... Args>
    constexpr String(Args... args):_str{ args... } { }
    const char _str[N];
};

//takes variadic number of chars, creates String object from it.
//i.e. myMakeStringFromChars('f', 'o', 'o', '\0') -> String<4>::_str = "foo"
template<typename... Args>
constexpr auto myMakeStringFromChars(Args... args) -> String<sizeof...(Args)> {
    return String<sizeof...(args)>(args...);
}

//This struct is here just because the iteration is going up instead of
//down. The solution was to mix traditional template metaprogramming
//with constexpr to be able to terminate the recursion since the template
//parameter N is needed in order to return the right-sized String<N>.
//This class exists only to dispatch on the recursion being finished or not.
//The default below continues recursion.
template<bool TERMINATE>
struct RecurseOrStop {
    template<size_t N, size_t I, typename... Args>
    static constexpr String<N> recurseOrStop(const char* str, Args... args);
};

//Specialisation to terminate recursion when all characters have been
//stripped from the string and converted to a variadic template parameter pack.
template<>
struct RecurseOrStop<true> {
    template<size_t N, size_t I, typename... Args>
    static constexpr String<N> recurseOrStop(const char* str, Args... args);
};

//Actual function to recurse over the string and turn it into a variadic
//parameter list of characters.
//Named differently to avoid infinite recursion.
template<size_t N, size_t I = 0, typename... Args>
constexpr String<N> myRecurseOrStop(const char* str, Args... args) {
    //template needed after :: since the compiler needs to distinguish
    //between recurseOrStop being a function template with 2 paramaters
    //or an enum being compared to N (recurseOrStop < N)
    return RecurseOrStop<I == N>::template recurseOrStop<N, I>(str, args...);
}

//implementation of the declaration above
//add a character to the end of the parameter pack and recurse to next character.
template<bool TERMINATE>
template<size_t N, size_t I, typename... Args>
constexpr String<N> RecurseOrStop<TERMINATE>::recurseOrStop(const char* str,
                                                            Args... args) {
    return myRecurseOrStop<N, I + 1>(str, args..., str[I]);
}

//implementation of the declaration above
//terminate recursion and construct string from full list of characters.
template<size_t N, size_t I, typename... Args>
constexpr String<N> RecurseOrStop<true>::recurseOrStop(const char* str,
                                                       Args... args) {
    return myMakeStringFromChars(args...);
}

//takes a compile-time static string literal and returns String<N> from it
//this happens by transforming the string literal into a variadic paramater
//pack of char.
//i.e. myMakeString("foo") -> calls myMakeStringFromChars('f', 'o', 'o', '\0');
template<size_t N>
constexpr String<N> myMakeString(const char (&str)[N]) {
    return myRecurseOrStop<N>(str);
}

//Simple tuple implementation. The only reason std::tuple isn't being used
//is because its only constexpr constructor is the default constructor.
//We need a constexpr constructor to be able to do compile-time shenanigans,
//and it's easier to roll our own tuple than to edit the standard library code.

//use MyTupleLeaf to construct MyTuple and make sure the order in memory
//is the same as the order of the variadic parameter pack passed to MyTuple.
template<typename T>
struct MyTupleLeaf {
    constexpr MyTupleLeaf(T value):_value(value) { }
    T _value;
};

//Use MyTupleLeaf implementation to define MyTuple.
//Won't work if used with 2 String<> objects of the same size but this
//is just a toy implementation anyway. Multiple inheritance guarantees
//data in the same order in memory as the variadic parameters.
template<typename... Args>
struct MyTuple: public MyTupleLeaf<Args>... {
    constexpr MyTuple(Args... args):MyTupleLeaf<Args>(args)... { }
};

//Helper function akin to std::make_tuple. Needed since functions can deduce
//types from parameter values, but classes can't.
template<typename... Args>
constexpr MyTuple<Args...> myMakeTuple(Args... args) {
    return MyTuple<Args...>(args...);
}

//Takes a variadic list of string literals and returns a tuple of String<> objects.
//These will be contiguous in memory. Trailing '\0' adds 1 to the size of each string.
//i.e. ("foo", "foobar") -> (const char (&arg1)[4], const char (&arg2)[7]) params ->
//                       ->  MyTuple<String<4>, String<7>> return value
template<size_t... Sizes>
constexpr auto myMakeStrings(const char (&...args)[Sizes]) -> MyTuple<String<Sizes>...> {
    //expands into myMakeTuple(myMakeString(arg1), myMakeString(arg2), ...)
    return myMakeTuple(myMakeString(args)...);
}

//Prints tuple of strings
template<typename T> //just to avoid typing the tuple type of the strings param
void printStrings(const T& strings) {
    //No std::get or any other helpers for MyTuple, so intead just cast it to
    //const char* to explore its layout in memory. We could add iterators to
    //myTuple and do "for(auto data: strings)" for ease of use, but the whole
    //point of this exercise is the memory layout and nothing makes that clearer
    //than the ugly cast below.
    const char* const chars = reinterpret_cast<const char*>(&strings);
    std::cout << "Printing strings of total size " << sizeof(strings);
    std::cout << " bytes:\n";
    std::cout << "-------------------------------\n";

    for(size_t i = 0; i < sizeof(strings); ++i) {
        chars[i] == '\0' ? std::cout << "\n" : std::cout << chars[i];
    }

    std::cout << "-------------------------------\n";
    std::cout << "\n\n";
}

int main() {
    {
        constexpr auto strings = myMakeStrings("foo", "foobar",
                                               "strings at compile time");
        printStrings(strings);
    }

    {
        constexpr auto strings = myMakeStrings("Some more strings",
                                               "just to show Jeff to not try",
                                               "to challenge C++11 again :P",
                                               "with more",
                                               "to show this is variadic");
        printStrings(strings);
    }

    std::cout << "Running 'objdump -t |grep my' should show that none of the\n";
    std::cout << "functions defined in this file (except printStrings()) are in\n";
    std::cout << "the executable. All computations are done by the compiler at\n";
    std::cout << "compile-time. printStrings() executes at run-time.\n";
}

คุณแน่ใจว่าเสร็จสิ้นในเวลารวบรวม? มีการพูดคุยเกี่ยวกับเรื่องนี้เมื่อไม่นานมานี้และสำหรับฉันแล้วผลลัพธ์ก็ยังไม่ชัดเจน
dyp

1
การวิ่งobjdump -t a.out |grep myไม่พบอะไรเลย เมื่อฉันเริ่มพิมพ์รหัสนี้ฉันยังคงทดลองลบออกconstexprจากฟังก์ชันและobjdumpแสดงให้เห็นเมื่อconstexprถูกละเว้น ฉันมั่นใจ 99.9% ที่เกิดขึ้นในเวลารวบรวม
Átila Neves

1
หากคุณดูการถอดชิ้นส่วน ( -S) คุณจะสังเกตเห็นว่า gcc (4.7.2) แก้ไขconstexprฟังก์ชันตามเวลาคอมไพล์ แต่ไม่ได้ประกอบสตริงในเวลาคอมไพล์ แต่ (ถ้าฉันตีความถูกต้อง) สำหรับแต่ละอักขระของสตริง "ประกอบ" เหล่านั้นมีการmovbดำเนินการของตัวเองซึ่งเป็นเนื้อหาที่คุณกำลังมองหา
dyp

2
นั่นคือเรื่องจริง ฉันลองอีกครั้งด้วย gcc 4.9 และมันก็ยังทำเหมือนเดิม ฉันคิดว่านี่เป็นคอมไพเลอร์ที่โง่เสมอเมื่อวานนี้ฉันคิดว่าจะลองคอมไพเลอร์อื่น ด้วยเสียงดังกราวภาพยนตร์ bytewise จะไม่มีเลย ด้วย gcc -Os ก็กำจัดพวกมันเช่นกัน แต่ -O3 ทำสิ่งเดียวกัน
Átila Neves

5

นี่คือวิธีแก้ปัญหา C ++ 14 แบบรวบรัดในการสร้าง std :: tuple <char ... > สำหรับแต่ละสตริงเวลาคอมไพล์ที่ผ่านไป

#include <tuple>
#include <utility>


namespace detail {
        template <std::size_t ... indices>
        decltype(auto) build_string(const char * str, std::index_sequence<indices...>) {
                return std::make_tuple(str[indices]...);
        }
}

template <std::size_t N>
constexpr decltype(auto) make_string(const char(&str)[N]) {
        return detail::build_string(str, std::make_index_sequence<N>());
}

auto HelloStrObject = make_string("hello");

และนี่คือหนึ่งในการสร้างประเภทเวลาคอมไพล์ที่ไม่ซ้ำใครซึ่งถูกตัดทอนจากโพสต์มาโครอื่น ๆ

#include <utility>

template <char ... Chars>
struct String {};

template <typename Str, std::size_t ... indices>
decltype(auto) build_string(std::index_sequence<indices...>) {
        return String<Str().chars[indices]...>();
}

#define make_string(str) []{\
        struct Str { const char * chars = str; };\
        return build_string<Str>(std::make_index_sequence<sizeof(str)>());\
}()

auto HelloStrObject = make_string("hello");

แย่มากที่ยังใช้ตัวอักษรที่ผู้ใช้กำหนดเองไม่ได้


จริงๆแล้วพวกเขาสามารถใช้ส่วนขยายที่รองรับโดย GCC / Clang แต่ฉันจะรอก่อนที่จะเพิ่มเข้าสู่มาตรฐานก่อนที่จะโพสต์เป็นคำตอบ
void-pointer

5

ดูเหมือนจะไม่มีใครชอบคำตอบอื่นของฉัน: - <. ที่นี่ฉันจะแสดงวิธีการแปลง str_const เป็นประเภทจริง:

#include <iostream>
#include <utility>

// constexpr string with const member functions
class str_const { 
private:
    const char* const p_;
    const std::size_t sz_;
public:

    template<std::size_t N>
    constexpr str_const(const char(&a)[N]) : // ctor
    p_(a), sz_(N-1) {}

    constexpr char operator[](std::size_t n) const { 
        return n < sz_ ? p_[n] :
        throw std::out_of_range("");
    }

    constexpr std::size_t size() const { return sz_; } // size()
};


template <char... letters>
struct string_t{
    static char const * c_str() {
        static constexpr char string[]={letters...,'\0'};
        return string;
    }
};

template<str_const const& str,std::size_t... I>
auto constexpr expand(std::index_sequence<I...>){
    return string_t<str[I]...>{};
}

template<str_const const& str>
using string_const_to_type = decltype(expand<str>(std::make_index_sequence<str.size()>{}));

constexpr str_const hello{"Hello World"};
using hello_t = string_const_to_type<hello>;

int main()
{
//    char c = hello_t{};        // Compile error to print type
    std::cout << hello_t::c_str();
    return 0;
}

คอมไพล์ด้วย clang ++ -stdlib = libc ++ -std = c ++ 14 (clang 3.7)


ใช้งานได้ดี แต่ไม่ใช่สำหรับ msvc 2019 เนื่องจากมีการบ่นเกี่ยวกับ str.size () ไม่ได้เป็น constexpr สามารถแก้ไขได้โดยการเพิ่มครั้งที่ 2 โดยใช้การอนุมาน str.size () แยกต่างหาก บางทีนั่นอาจเป็นการระงับการโหวตเพิ่มขึ้นบ้าง ;-)
Zacharias

2

จากแนวคิดของHoward Hinnantคุณสามารถสร้างคลาสตัวอักษรที่จะเพิ่มตัวอักษรสองตัวเข้าด้วยกัน

template<int>
using charDummy = char;

template<int... dummy>
struct F
{
    const char table[sizeof...(dummy) + 1];
    constexpr F(const char* a) : table{ str_at<dummy>(a)..., 0}
    {

    }
    constexpr F(charDummy<dummy>... a) : table{ a..., 0}
    {

    }

    constexpr F(const F& a) : table{ a.table[dummy]..., 0}
    {

    }

    template<int... dummyB>
    constexpr F<dummy..., sizeof...(dummy)+dummyB...> operator+(F<dummyB...> b)
    {
        return { this->table[dummy]..., b.table[dummyB]... };
    }
};

template<int I>
struct get_string
{
    constexpr static auto g(const char* a) -> decltype( get_string<I-1>::g(a) + F<0>(a + I))
    {
        return get_string<I-1>::g(a) + F<0>(a + I);
    }
};

template<>
struct get_string<0>
{
    constexpr static F<0> g(const char* a)
    {
        return {a};
    }
};

template<int I>
constexpr auto make_string(const char (&a)[I]) -> decltype( get_string<I-2>::g(a) )
{
    return get_string<I-2>::g(a);
}

constexpr auto a = make_string("abc");
constexpr auto b = a+ make_string("def"); // b.table == "abcdef" 

ที่ได้str_atมาจากไหน
mic_e

มันเป็นแบบนั้น:str_at<int I>(const char* a) { return a[i]; }
Yankes

2

แนวทาง # 1 ของคุณเป็นแนวทางที่ถูกต้อง

อย่างไรก็ตามอาร์เรย์จะต้องมีการเชื่อมโยงภายนอกดังนั้นเพื่อให้แนวทางที่ 1 ทำงานได้เราจะต้องเขียนดังนี้: constexpr const char str [] = "สวัสดีชาวโลก!";

ไม่ถูกต้อง ซึ่งรวบรวมด้วย clang และ gcc ฉันหวังว่ามันจะเป็นมาตรฐาน c ++ 11 แต่ฉันไม่ใช่คนวางภาษา

#include <iostream>

template <char... letters>
struct string_t{
    static char const * c_str() {
        static constexpr char string[]={letters...,'\0'};
        return string;
    }
};

// just live with it, but only once
using Hello_World_t = string_t<'H','e','l','l','o',' ','w','o','r','l','d','!'>;

template <typename Name>
void print()
{
    //String as template parameter
    std::cout << Name::c_str();
}

int main() {
    std::cout << Hello_World_t::c_str() << std::endl;
    print<Hello_World_t>();
    return 0;
}

สิ่งที่ฉันชอบจริงๆสำหรับ c ++ 17 คือสิ่งต่อไปนี้จะเทียบเท่า (เพื่อให้แนวทางที่ 1 สมบูรณ์)

// for template <char...>
<"Text"> == <'T','e','x','t'>

สิ่งที่คล้ายกันมากมีอยู่แล้วในมาตรฐานสำหรับตัวอักษรที่กำหนดโดยผู้ใช้เทมเพลตเนื่องจาก void-pointer ยังกล่าวถึง แต่สำหรับตัวเลขเท่านั้น ก่อนหน้านี้เคล็ดลับเล็ก ๆ น้อย ๆ อีกประการหนึ่งคือการใช้โหมดการแก้ไขแทนที่ + คัดลอกและวาง

string_t<' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '>;

หากคุณไม่สนใจมาโครการใช้งานมากกว่านี้ (แก้ไขเล็กน้อยจากคำตอบของ Yankes):

#define MACRO_GET_1(str, i) \
(sizeof(str) > (i) ? str[(i)] : 0)

#define MACRO_GET_4(str, i) \
MACRO_GET_1(str, i+0),  \
MACRO_GET_1(str, i+1),  \
MACRO_GET_1(str, i+2),  \
MACRO_GET_1(str, i+3)

#define MACRO_GET_16(str, i) \
MACRO_GET_4(str, i+0),   \
MACRO_GET_4(str, i+4),   \
MACRO_GET_4(str, i+8),   \
MACRO_GET_4(str, i+12)

#define MACRO_GET_64(str, i) \
MACRO_GET_16(str, i+0),  \
MACRO_GET_16(str, i+16), \
MACRO_GET_16(str, i+32), \
MACRO_GET_16(str, i+48)

//CT_STR means Compile-Time_String
#define CT_STR(str) string_t<MACRO_GET_64(#str, 0), 0 >//guard for longer strings

print<CT_STR(Hello World!)>();

2

โซลูชันของ kacey สำหรับการสร้างประเภทเวลาคอมไพล์ที่ไม่ซ้ำกันสามารถใช้กับ C ++ 11 ได้ด้วย:

template <char... Chars>
struct string_t {};

namespace detail {
template <typename Str,unsigned int N,char... Chars>
struct make_string_t : make_string_t<Str,N-1,Str().chars[N-1],Chars...> {};

template <typename Str,char... Chars>
struct make_string_t<Str,0,Chars...> { typedef string_t<Chars...> type; };
} // namespace detail

#define CSTR(str) []{ \
    struct Str { const char *chars = str; }; \
    return detail::make_string_t<Str,sizeof(str)>::type(); \
  }()

ใช้:

template <typename String>
void test(String) {
  // ... String = string_t<'H','e','l','l','o','\0'>
}

test(CSTR("Hello"));

2

ในขณะที่เล่นกับแผนที่บูสต์ฮานะฉันเจอกระทู้นี้ เนื่องจากคำตอบที่ไม่สามารถแก้ปัญหาของฉันได้ฉันจึงพบวิธีแก้ปัญหาอื่นซึ่งฉันต้องการเพิ่มที่นี่เนื่องจากอาจเป็นประโยชน์สำหรับผู้อื่น

ปัญหาของฉันคือเมื่อใช้แผนที่เพิ่มฮานะกับสตริงฮานะคอมไพเลอร์ยังคงสร้างโค้ดรันไทม์บางส่วน (ดูด้านล่าง) constexprเหตุผลที่เห็นได้ชัดว่าในการค้นหาแผนที่ที่รวบรวมเวลาที่มันต้องเป็น สิ่งนี้เป็นไปไม่ได้เนื่องจากBOOST_HANA_STRINGมาโครสร้างแลมด้าซึ่งไม่สามารถใช้ในconstexprบริบทได้ ในทางกลับกันแผนที่ต้องการสตริงที่มีเนื้อหาต่างกันเพื่อให้เป็นประเภทต่างๆ

เนื่องจากโซลูชันในเธรดนี้ใช้แลมด้าหรือไม่ได้ระบุประเภทที่แตกต่างกันสำหรับเนื้อหาที่แตกต่างกันฉันจึงพบว่าแนวทางต่อไปนี้มีประโยชน์ นอกจากนี้ยังหลีกเลี่ยงstr<'a', 'b', 'c'>ไวยากรณ์ที่แฮ็ก

แนวคิดพื้นฐานคือการมีstr_constเทมเพลตของ Scott Schurr ในเวอร์ชันแฮชของตัวละคร เป็นc++14ไปได้ แต่c++11ควรเป็นไปได้ด้วยการใช้crc32ฟังก์ชันซ้ำ(ดูที่นี่ )

// str_const from https://github.com/boostcon/cppnow_presentations_2012/blob/master/wed/schurr_cpp11_tools_for_class_authors.pdf?raw=true

    #include <string>

template<unsigned Hash>  ////// <- This is the difference...
class str_const2 { // constexpr string
private:
    const char* const p_;
    const std::size_t sz_;
public:
    template<std::size_t N>
    constexpr str_const2(const char(&a)[N]) : // ctor
        p_(a), sz_(N - 1) {}


    constexpr char operator[](std::size_t n) const { // []
        return n < sz_ ? p_[n] :
            throw std::out_of_range("");
    }

    constexpr std::size_t size() const { return sz_; } // size()

    constexpr const char* const data() const {
        return p_;
    }
};

// Crc32 hash function. Non-recursive version of https://stackoverflow.com/a/23683218/8494588
static constexpr unsigned int crc_table[256] = {
    0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
    0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
    0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
    0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
    0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
    0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
    0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
    0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
    0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
    0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
    0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
    0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
    0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
    0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
    0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
    0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
    0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
    0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
    0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
    0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
    0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
    0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
    0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
    0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
    0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
    0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
    0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
    0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
    0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
    0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
    0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
    0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
    0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
    0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
    0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
    0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
    0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
    0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
    0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
    0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
    0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
    0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
    0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
};

template<size_t N>
constexpr auto crc32(const char(&str)[N])
{
    unsigned int prev_crc = 0xFFFFFFFF;
    for (auto idx = 0; idx < sizeof(str) - 1; ++idx)
        prev_crc = (prev_crc >> 8) ^ crc_table[(prev_crc ^ str[idx]) & 0xFF];
    return prev_crc ^ 0xFFFFFFFF;
}

// Conveniently create a str_const2
#define CSTRING(text) str_const2 < crc32( text ) >( text )

// Conveniently create a hana type_c<str_const2> for use in map
#define CSTRING_TYPE(text) hana::type_c<decltype(str_const2 < crc32( text ) >( text ))>

การใช้งาน:

#include <boost/hana.hpp>

#include <boost/hana/map.hpp>
#include <boost/hana/pair.hpp>
#include <boost/hana/type.hpp>

namespace hana = boost::hana;

int main() {

    constexpr auto s2 = CSTRING("blah");

    constexpr auto X = hana::make_map(
        hana::make_pair(CSTRING_TYPE("aa"), 1)
    );    
    constexpr auto X2 = hana::insert(X, hana::make_pair(CSTRING_TYPE("aab"), 2));   
    constexpr auto ret = X2[(CSTRING_TYPE("aab"))];
    return ret;
}

รหัสแอสเซมเบลอร์ผลลัพธ์ที่มีclang-cl5.0 คือ:

012A1370  mov         eax,2  
012A1375  ret  

0

ฉันต้องการเพิ่มการปรับปรุงเล็ก ๆ สองอย่างสำหรับคำตอบของ @ user1115339 ฉันพูดถึงพวกเขาในความคิดเห็นของคำตอบ แต่เพื่อความสะดวกฉันจะใส่วิธีคัดลอกวางที่นี่

ข้อแตกต่างเพียงอย่างเดียวคือFIXED_CSTRINGมาโครซึ่งอนุญาตให้ใช้สตริงภายในเทมเพลตคลาสและเป็นอาร์กิวเมนต์ของตัวดำเนินการดัชนี (มีประโยชน์หากคุณมีเช่นแผนที่คอมไพเลไทม์)

ตัวอย่างสด

namespace  variadic_toolbox
{
    template<unsigned  count, 
        template<unsigned...> class  meta_functor, unsigned...  indices>
    struct  apply_range
    {
        typedef  typename apply_range<count-1, meta_functor, count-1, indices...>::result  result;
    };

    template<template<unsigned...> class  meta_functor, unsigned...  indices>
    struct  apply_range<0, meta_functor, indices...>
    {
        typedef  typename meta_functor<indices...>::result  result;
    };
}

namespace  compile_time
{
    template<char...  str>
    struct  string
    {
        static  constexpr  const char  chars[sizeof...(str)+1] = {str..., '\0'};
    };

    template<char...  str>
    constexpr  const char  string<str...>::chars[sizeof...(str)+1];

    template<typename  lambda_str_type>
    struct  string_builder
    {
        template<unsigned... indices>
        struct  produce
        {
            typedef  string<lambda_str_type{}.chars[indices]...>  result;
        };
    };
}

#define  CSTRING(string_literal)                                                        \
    []{                                                                                 \
        struct  constexpr_string_type { const char * chars = string_literal; };         \
        return  variadic_toolbox::apply_range<sizeof(string_literal)-1,                 \
            compile_time::string_builder<constexpr_string_type>::produce>::result{};    \
    }()


#define  FIXED_CSTRING(string_literal)                                                        \
    ([]{                                                                                 \
        struct  constexpr_string_type { const char * chars = string_literal; };         \
        return  typename variadic_toolbox::apply_range<sizeof(string_literal)-1,                 \
            compile_time::string_builder<constexpr_string_type>::template produce>::result{};    \
    }())    

struct A {

    auto test() {
        return FIXED_CSTRING("blah"); // works
        // return CSTRING("blah"); // works too
    }

    template<typename X>
    auto operator[](X) {
        return 42;
    }
};

template<typename T>
struct B {

    auto test() {       
       // return CSTRING("blah");// does not compile
       return FIXED_CSTRING("blah"); // works
    }
};

int main() {
    A a;
    //return a[CSTRING("blah")]; // fails with error: two consecutive ' [ ' shall only introduce an attribute before ' [ ' token
    return a[FIXED_CSTRING("blah")];
}

0

การใช้งานของฉันเองขึ้นอยู่กับแนวทางจากBoost.Hanaสตริง (คลาสเทมเพลตที่มีอักขระตัวแปร) แต่ใช้เฉพาะC++11มาตรฐานและconstexprฟังก์ชันที่มีการตรวจสอบความสมบูรณ์อย่างเข้มงวด (อาจเป็นข้อผิดพลาดเวลาคอมไพล์หากไม่ใช่นิพจน์เวลาคอมไพล์) สามารถสร้างจากสตริง C ดิบปกติแทนแฟนซี{'a', 'b', 'c' }(ผ่านมาโคร)

การนำไปใช้งาน: https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/include/tacklelib/tackle/tmpl_string.hpp

การทดสอบ: https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/src/tests/unit/test_tmpl_string.cpp

ตัวอย่างการใช้งาน:

const auto s0    = TACKLE_TMPL_STRING(0, "012");            // "012"
const char c1_s0 = UTILITY_CONSTEXPR_GET(s0, 1);            // '1'

const auto s1    = TACKLE_TMPL_STRING(0, "__012", 2);       // "012"
const char c1_s1 = UTILITY_CONSTEXPR_GET(s1, 1);            // '1'

const auto s2    = TACKLE_TMPL_STRING(0, "__012__", 2, 3);  // "012"
const char c1_s2 = UTILITY_CONSTEXPR_GET(s2, 1);            // '1'

// TACKLE_TMPL_STRING(0, "012") and TACKLE_TMPL_STRING(1, "012")
//   - semantically having different addresses.
//   So id can be used to generate new static array class field to store
//   a string bytes at different address.

// Can be overloaded in functions with another type to express the compiletimeness between functions:

template <uint64_t id, typename CharT, CharT... tchars>
const overload_resolution_1 & test_overload_resolution(const tackle::tmpl_basic_string<id, CharT, tchars...> &);
template <typename CharT>
const overload_resolution_2 & test_overload_resolution(const tackle::constexpr_basic_string<CharT> &);

// , where `constexpr_basic_string` is another approach which loses
//   the compiletimeness between function signature and body border,
//   because even in a `constexpr` function the compile time argument
//   looses the compiletimeness nature and becomes a runtime one.

รายละเอียดเกี่ยวกับconstexprเส้นขอบเวลาคอมไพล์ของฟังก์ชัน: https://www.boost.org/doc/libs/1_65_0/libs/hana/doc/html/index.html#tutorial-appendix-constexpr

สำหรับรายละเอียดการใช้งานอื่น ๆ โปรดดูการทดสอบ

ขณะนี้โครงการทั้งหมดอยู่ระหว่างการทดลอง


0

ใน C ++ 17 ที่มีฟังก์ชันมาโครตัวช่วยคุณสามารถสร้างสตริงเวลาคอมไพล์ได้อย่างง่ายดาย:

template <char... Cs>
struct ConstexprString
{
    static constexpr int size = sizeof...( Cs );
    static constexpr char buffer[size] = { Cs... };
};

template <char... C1, char... C2>
constexpr bool operator==( const ConstexprString<C1...>& lhs, const ConstexprString<C2...>& rhs )
{
    if( lhs.size != rhs.size )
        return false;

    return std::is_same_v<std::integer_sequence<char, C1...>, std::integer_sequence<char, C2...>>;
}




template <typename F, std::size_t... Is>
constexpr auto ConstexprStringBuilder( F f, std::index_sequence<Is...> )
{
    return ConstexprString<f( Is )...>{};
}

#define CONSTEXPR_STRING( x )                                              \
  ConstexprStringBuilder( []( std::size_t i ) constexpr { return x[i]; },  \
                 std::make_index_sequence<sizeof(x)>{} )

และนี่คือตัวอย่างการใช้งาน:

auto n = CONSTEXPR_STRING( "ab" );
auto m = CONSTEXPR_STRING( "ab" );


static_assert(n == m);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.