컴파일 타임에`const char *`유형의 두 문자열을 연결할 수 있습니까?


12

분명히 constexpr함수 에서 두 개의 문자열 리터럴을 연결할 수 있지만 constexpr아래 코드와 같이 다른 함수에서 반환 된 문자열과 문자열 리터럴을 연결하는 것은 어떻습니까?

template <class T>
constexpr const char * get_arithmetic_size()
{
    switch (sizeof(T))
    {
    case 1: return "1";
    case 2: return "2";
    case 4: return "4";
    case 8: return "8";
    case 16: return "16";
    default: static_assert(dependent_false_v<T>);
    }
}

template <class T>
constexpr std::enable_if_t<std::is_arithmetic_v<T>, const char *> make_type_name()
{
    const char * prefix = std::is_signed_v<T> ? "int" : "uint";
    return prefix; // how to concatenate prefix with get_arithmetic_size<T>() ?
}

static_assert(strings_equal(make_type_name<int>, make_type_name<int32_t>);

이 코드는 컴파일러 독립적 인 문자열 식별자를 산술 유형으로 만듭니다.

편집 1 :

좀 더 복잡한 예는 다음과 같습니다.

template<typename Test, template<typename...> class Ref>
struct is_specialization : std::false_type {};

template<template<typename...> class Ref, typename... Args>
struct is_specialization<Ref<Args...>, Ref> : std::true_type {};

template <class T>
constexpr std::enable_if_t<is_specialization<T, std::vector>::value || is_specialization<T, std::list>::value, const char *> make_type_name()
{
    return "sequence"; // + make_type_name<typename T::value_type>;
}

static_assert(strings_equal(make_type_name<std::vector<int>>(), make_type_name<std::list<int>>()));

2
바이트를 포함하는 표준 배열이 허용됩니까? 그렇지 않으면 매크로와 코드 생성을 사용하여이를 수행 할 수 있습니다.
Yakk-Adam Nevraumont

@ Yakk-AdamNevraumont 네, 더 나은 솔루션이없는 것 같습니다 std::array(아마도 + 가변 템플릿)
Dmitriano

@ Yakk-AdamNevraumont 연결 std :: arrays : stackoverflow.com/questions/42749032/… , 라이브 예 : wandbox.org/permlink/VA85KCTqxiyS2rKE
Dmitriano

1
본질적으로 typeid연산자 와 동일한 유형의 결과를 달성하는 무언가를 수동으로 굴 리려고하는 것 같습니다 . 이유 typeid중 일부는 라이브러리 함수가 아닌 언어의 일부입니다 (예 : 전용 언어 키워드로 지원됨). .
피터

1
@Dmitriano 예, 당신은 내가 그 질문을 전에 본 적이 있음을 알 것입니다 ;)
Yakk-Adam Nevraumont

답변:


4

빠른 컴파일 타임 문자열 클래스는 다음과 같습니다.

template<std::size_t N>
struct ct_str
{
    char state[N+1] = {0};
    constexpr ct_str( char const(&arr)[N+1] )
    {
        for (std::size_t i = 0; i < N; ++i)
            state[i] = arr[i];
    }
    constexpr char operator[](std::size_t i) const { return state[i]; } 
    constexpr char& operator[](std::size_t i) { return state[i]; } 

    constexpr explicit operator char const*() const { return state; }
    constexpr char const* data() const { return state; }
    constexpr std::size_t size() const { return N; }
    constexpr char const* begin() const { return state; }
    constexpr char const* end() const { return begin()+size(); }

    constexpr ct_str() = default;
    constexpr ct_str( ct_str const& ) = default;
    constexpr ct_str& operator=( ct_str const& ) = default;

    template<std::size_t M>
    friend constexpr ct_str<N+M> operator+( ct_str lhs, ct_str<M> rhs )
    {
        ct_str<N+M> retval;
        for (std::size_t i = 0; i < N; ++i)
            retval[i] = lhs[i];
        for (std::size_t i = 0; i < M; ++i)
            retval[N+i] = rhs[i];
        return retval;
    }

    friend constexpr bool operator==( ct_str lhs, ct_str rhs )
    {
        for (std::size_t i = 0; i < N; ++i)
            if (lhs[i] != rhs[i]) return false;
        return true;
    }
    friend constexpr bool operator!=( ct_str lhs, ct_str rhs )
    {
        for (std::size_t i = 0; i < N; ++i)
            if (lhs[i] != rhs[i]) return true;
        return false;
    }
    template<std::size_t M, std::enable_if_t< M!=N, bool > = true>
    friend constexpr bool operator!=( ct_str lhs, ct_str<M> rhs ) { return true; }
    template<std::size_t M, std::enable_if_t< M!=N, bool > = true>
    friend bool operator==( ct_str, ct_str<M> ) { return false; }
};

template<std::size_t N>
ct_str( char const(&)[N] )->ct_str<N-1>;

다음과 같이 사용할 수 있습니다.

template <class T>
constexpr auto get_arithmetic_size()
{
    if constexpr (sizeof(T)==1)
        return ct_str{"1"};
    if constexpr (sizeof(T)==2)
        return ct_str{"2"};
    if constexpr (sizeof(T)==4)
        return ct_str{"4"};
    if constexpr (sizeof(T)==8)
        return ct_str{"8"};
    if constexpr (sizeof(T)==16)
        return ct_str{"16"};
}

template <class T, std::enable_if_t<std::is_arithmetic<T>{}, bool> = true>
constexpr auto make_type_name()
{
    if constexpr (std::is_signed<T>{})
        return ct_str{"int"} + get_arithmetic_size<T>();
    else
        return ct_str{"uint"} + get_arithmetic_size<T>();
}

다음과 같은 진술로 이어집니다.

static_assert(make_type_name<int>() == make_type_name<int32_t>());

통과.

라이브 예 .

이제 성가신 것은 버퍼의 길이가 타입 시스템에 있다는 것입니다. length필드를 추가하고 N"버퍼 크기"로 만들 수 있으며 후행 바이트 ct_str만 복사하여 length그대로 두도록 수정할 수 0있습니다. 그런 다음 양쪽 common_type의 최대 값을 반환하도록 재정의 하십시오 N.

그렇게 하면 동일한 유형의 값을 전달 ct_str{"uint"}하고 ct_str{"int"}구현 코드를 약간 성가 시게 할 수 있습니다.

template<std::size_t N>
struct ct_str
{
    char state[N+1] = {0};

    template<std::size_t M, std::enable_if_t< (M<=N+1), bool > = true>
    constexpr ct_str( char const(&arr)[M] ):
        ct_str( arr, std::make_index_sequence<M>{} )
    {}
    template<std::size_t M, std::enable_if_t< (M<N), bool > = true >
    constexpr ct_str( ct_str<M> const& o ):
        ct_str( o, std::make_index_sequence<M>{} )
    {}
private:
    template<std::size_t M, std::size_t...Is>
    constexpr ct_str( char const(&arr)[M], std::index_sequence<Is...> ):
        state{ arr[Is]... }
    {}
    template<std::size_t M, std::size_t...Is>
    constexpr ct_str( ct_str<M> const& o, std::index_sequence<Is...> ):
        state{ o[Is]... }
    {}
public:
    constexpr char operator[](std::size_t i) const { return state[i]; } 
    constexpr char& operator[](std::size_t i) { return state[i]; } 

    constexpr explicit operator char const*() const { return state; }
    constexpr char const* data() const { return state; }
    constexpr std::size_t size() const {
        std::size_t retval = 0;
        while(state[retval]) {
            ++retval;
        }
        return retval;
    }
    constexpr char const* begin() const { return state; }
    constexpr char const* end() const { return begin()+size(); }

    constexpr ct_str() = default;
    constexpr ct_str( ct_str const& ) = default;
    constexpr ct_str& operator=( ct_str const& ) = default;

    template<std::size_t M>
    friend constexpr ct_str<N+M> operator+( ct_str lhs, ct_str<M> rhs )
    {
        ct_str<N+M> retval;
        for (std::size_t i = 0; i < lhs.size(); ++i)
            retval[i] = lhs[i];
        for (std::size_t i = 0; i < rhs.size(); ++i)
            retval[lhs.size()+i] = rhs[i];
        return retval;
    }

    template<std::size_t M>
    friend constexpr bool operator==( ct_str lhs, ct_str<M> rhs )
    {
        if (lhs.size() != rhs.size()) return false;
        for (std::size_t i = 0; i < lhs.size(); ++i)
            if (lhs[i] != rhs[i]) return false;
        return true;
    }
    template<std::size_t M>
    friend constexpr bool operator!=( ct_str lhs, ct_str<M> rhs )
    {
        if (lhs.size() != rhs.size()) return true;
        for (std::size_t i = 0; i < lhs.size(); ++i)
            if (lhs[i] != rhs[i]) return true;
        return false;
    }
};

template<std::size_t N>
ct_str( char const(&)[N] )->ct_str<N-1>;

함수 구현은 이제 다음과 같습니다.

template <class T>
constexpr ct_str<2> get_arithmetic_size()
{
    switch (sizeof(T)) {
        case 1: return "1";
        case 2: return "2";
        case 4: return "4";
        case 8: return "8";
        case 16: return "16";
    }

}

template <class T, std::enable_if_t<std::is_arithmetic<T>{}, bool> = true>
constexpr auto make_type_name()
{
    constexpr auto base = std::is_signed<T>{}?ct_str{"int"}:ct_str{"uint"};
    return base + get_arithmetic_size<T>();
}

작성하는 것이 훨씬 더 자연 스럽습니다.

라이브 예 .


멋있는! 그것은 사용하는 것이 좋습니다 elseget_arithmetic_sizeif constexpr당신이 할 경우에도 return하지 않고 있기 때문에, else어설 션이 dependent_false_v<T>실패합니다.
Dmitriano

두 번째 대안은 매우 시원합니다!
Dmitriano

4

아니요, 불가능합니다. 아래와 같은 것을 구현할 수 있습니다 (C ++ 14).

#include <cmath>
#include <cstring>
#include <iostream>
#include <type_traits>

constexpr const char* name[] = {
  "uint1", "uint2", "uint4", "uint8", "uint16",
  "int1",  "int2",  "int4",  "int8",  "int16"
};

template <class T>
constexpr std::enable_if_t<std::is_arithmetic<T>::value, const char *> make_type_name() {
  return name[std::is_signed<T>::value * 5 +
    static_cast<int>(std::log(sizeof(T)) / std::log(2) + 0.5)];
}

static_assert(std::strcmp(make_type_name<int>(), make_type_name<int32_t>()) == 0);

int main() {
  std::cout << make_type_name<int>();
  return 0;
}

https://ideone.com/BaADaM

를 사용하지 않으 <cmath>려면 다음을 대체하십시오 std::log.

#include <cstring>
#include <iostream>
#include <type_traits>

constexpr const char* name[] = {
  "uint1", "uint2", "uint4", "uint8", "uint16",
  "int1",  "int2",  "int4",  "int8",  "int16"
};

constexpr size_t log2(size_t n) {
  return (n<2) ? 0 : 1 + log2(n/2);
}

template <class T>
constexpr std::enable_if_t<std::is_arithmetic<T>::value, const char *> make_type_name() {
  return name[std::is_signed<T>::value * 5 + log2(sizeof(T))];
}

static_assert(std::strcmp(make_type_name<int>(), make_type_name<int32_t>()) == 0);

int main() {
  std::cout << make_type_name<int>();
  return 0;
}

std::log나를 위해 너무 복잡하다, 문자열을 연결하는 몇 가지 일반적인 기술이 필요합니다
Dmitriano

그것은이다 constexpr,에 대해 걱정하지 마십시오 std::log(). 이를 대체 할 수는 있지만 코드가 확대됩니다.
SM

EDIT1에 대한 예가 있습니까?
Dmitriano

4
내가 아는 한, 나도 보장 std::log되지 std::strcmp는 않는다 constexpr. 실제로이 표준은 특히 constexprC ++ 14 이후 의 표준을 금지합니다 . 따라서 코드는 실제로 비표준 확장을 사용합니다.
LF
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.