C ++에서 컴파일 타임 문자열을 편리하게 선언 here. */ constexpr const char str[] = “Hello,

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는 실제로 구현할 수 없습니다. ( 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유형 이 아니라는 문제가 발생합니다 .

문자열과 문자열의 크기를 인수로 사용하고 문자열의 문자로 구성된 시퀀스 (, 문자열 BOOST_PP_FOR화, 배열 첨자 등)를 반환하는 C 전 처리기 매크로를 정의하는 것이 가능해야한다고 생각합니다 . 그러나, 나는 그러한 매크로를 구현할 시간 (또는 충분한 관심)이 없습니다 =)



답변

나는 C ++ Now 2012 에서 발표 된 Scott Schurr의str_const 우아함과 어울리는 것을 보지 못했습니다 . 그래도 필요합니다 .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의 암시 적 저작권을 존중합니다. 구현은 위에 링크 된 프레젠테이션의 단일 슬라이드에 있습니다.


답변

문자열과 문자열 크기를 인수로 사용하고 문자열의 문자로 구성된 시퀀스 (BOOST_PP_FOR, 문자열 화, 배열 첨자 등 사용)를 반환하는 C 전 처리기 매크로를 정의하는 것이 가능해야한다고 생각합니다. 그러나 그러한 매크로를 구현할 시간이 충분하지 않습니다.

매우 간단한 매크로와 일부 C ++ 11 기능을 사용하여 부스트에 의존하지 않고 이것을 구현할 수 있습니다.

  1. 람다
  2. 템플릿
  3. 일반화 된 상수 표현식
  4. 비 정적 데이터 멤버 이니셜 라이저
  5. 균일 한 초기화

(후자의 2 개는 여기서 엄격하게 요구되지는 않습니다)

  1. 우리는 사용자가 제공 한 인덱스를 0에서 N까지의 variadic 템플릿으로 인스턴스화 할 수 있어야합니다. 예를 들어 튜플을 variadic 템플릿 함수의 인수로 확장하는 데 유용한 도구도 있습니다 (질문 : 튜플을 variadic 템플릿 함수의 인수로 확장하는 방법은 무엇입니까?
    ” “튜플을 풀고하는 것은 일치하는 함수 포인터 호출 )

    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로 string이라는 가변 템플릿을 정의하십시오.

    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


답변

편집 : 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);
}

// helper "function" struct
template < char t_c, char... tt_c >
struct rec_print
{
    static void print()
    {
        std::cout << t_c;
        rec_print < tt_c... > :: print ();
    }
};
    template < char t_c >
    struct rec_print < t_c >
    {
        static void print() { std::cout << t_c; }
    };


// destination "template string" type
template < char... tt_c >
struct exploded_string
{
    static void print()
    {
        rec_print < tt_c... > :: print();
    }
};

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

    template < typename T_StrProvider, char... tt_c >
    struct explode_impl < T_StrProvider, 0, tt_c... >
    {
         using result = exploded_string < tt_c... >;
    };

// syntactical sugar
template < typename T_StrProvider >
using explode =
    typename explode_impl < T_StrProvider,
                            c_strlen(T_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();
}


답변

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 자 (추가 0)입니다. 그러나 필요에 따라 쉽게 변경할 수 있습니다.


답변

문자열과 문자열 크기를 인수로 사용하고 문자열의 문자로 구성된 시퀀스를 반환하는 C 전 처리기 매크로를 정의 할 수 있어야한다고 생각합니다 (BOOST_PP_FOR, 문자열 화, 배열 첨자 등 사용).

기사 : Abel Sinkovics와 Dave Abrahams의 C ++ 템플릿 메타 프로그램에서 문자열 사용 .

매크로 + BOOST_PP_REPEAT 사용에 대한 아이디어가 약간 개선되었습니다 . 매크로에 명시적인 크기를 전달할 필요가 없습니다. 즉, 문자열 크기 및 “문자열 오버런 방지”에 대한 고정 된 상한을 기반으로합니다.

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

플러스 조건부 부스트 :: mpl :: push_back .


이 특정 문제를 해결하고 constexpr 또는 복잡한 전 처리기 코드를 사용하지 않고 우아하게 처리하기 때문에 Yankes의 솔루션으로 허용되는 답변을 변경했습니다.

후행 0, 손으로 쓴 매크로 반복, 확장 된 매크로에서 문자열의 2 배 반복을 수락 하고 Boost가 없다면 동의합니다. 그러나 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


답변

아무도 내 다른 대답을 좋아하지 않는 것 같습니다 :-<. 그래서 여기 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)로 컴파일


답변

다음은 전달 된 각 컴파일 타임 문자열에 대해 std :: tuple <char …>을 만드는 간결한 C ++ 14 솔루션입니다.

#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");

사용자 정의 리터럴을 아직 사용할 수 없다는 것이 너무 나쁩니다.