Có thể nối hai chuỗi kiểu `const char *` tại thời gian biên dịch không?


12

Rõ ràng chúng ta có thể nối hai chuỗi ký tự trong một constexprhàm, nhưng còn việc nối chuỗi của một chuỗi ký tự với một chuỗi được trả về bởi một constexprhàm khác như trong đoạn mã dưới đây thì sao?

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

Mã này làm cho định danh chuỗi độc lập của trình biên dịch thuộc loại số học.

EDIT1:

Một ví dụ phức tạp hơn một chút là:

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
Một mảng std có chứa các byte được chấp nhận? Nếu không, bạn có thể sử dụng macro và mã gen để làm điều này.
Yakk - Adam Nevraumont

@ Yakk-AdamNevraumont có, có vẻ như không có giải pháp nào tốt hơn std::array(và có thể là + các mẫu
dao động

@ Yakk-AdamNevraumont ghép nối std :: mảng: stackoverflow.com/questions/42749032/ , ví dụ trực tiếp: Wandbox.org/permlink/VA85KCTqxiyS2rKE
Dmitriano

1
Có vẻ như bạn về cơ bản đang cố gắng cuộn một cái gì đó đạt được cùng loại kết quả như typeidtoán tử. Một phần lý do typeidlà một phần của ngôn ngữ (ví dụ: được hỗ trợ bởi một từ khóa ngôn ngữ chuyên dụng) chứ không phải là chức năng thư viện là việc triển khai nó phụ thuộc vào "ma thuật trình biên dịch" - không thể thực hiện bằng ngôn ngữ mà không có sự hỗ trợ chuyên dụng từ việc triển khai .
Peter

1
@Dmitriano Vâng, bạn có thể nhận thấy tôi đã thấy câu hỏi đó trước đây ;)
Yakk - Adam Nevraumont

Câu trả lời:


4

Đây là một lớp chuỗi thời gian biên dịch nhanh:

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>;

bạn có thể sử dụng nó như thế này:

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>();
}

dẫn đến các tuyên bố như:

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

đi qua.

Ví dụ sống .

Bây giờ một điều khó chịu là chiều dài của bộ đệm nằm trong hệ thống loại. Bạn có thể thêm một lengthtrường và tạo N"kích thước bộ đệm" và sửa đổi ct_strđể chỉ sao chép tối đa lengthvà để lại các byte theo sau 0. Sau đó ghi đè common_typeđể trả lại tối đa Ncủa cả hai bên.

Điều đó sẽ cho phép bạn vượt qua ct_str{"uint"}ct_str{"int"}trong cùng loại giá trị và làm cho mã triển khai bớt khó chịu hơn một chút.

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>;

Việc triển khai chức năng bây giờ trở thành:

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>();
}

đó là rất nhiều tự nhiên để viết.

Ví dụ sống .


Mát mẻ! Nó là tốt hơn để sử dụng elsetrong get_arithmetic_sizevới if constexprngay cả khi bạn làm return, bởi vì không có elsesự khẳng định dependent_false_v<T>sẽ thất bại.
Dmitriano

Sự thay thế thứ hai là cực kỳ mát mẻ!
Dmitriano

4

Không, không thể. Bạn có thể thực hiện một cái gì đó như dưới đây (đó là 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

Nếu bạn không thích sử dụng <cmath>, bạn có thể thay thế 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::logquá phức tạp đối với tôi, cần một số kỹ thuật chung để nối chuỗi
Dmitriano

Đó là một constexpr, đừng lo lắng về std::log(). Bạn có thể thay thế nó, nhưng mã sẽ được mở rộng,
SM

Bạn có một ví dụ cho EDIT1 không?
Dmitriano

4
Theo hiểu biết tốt nhất của tôi, không phải std::logcũng không std::strcmpđược đảm bảo constexpr. Trong thực tế, tiêu chuẩn đặc biệt cấm họ constexprkể từ C ++ 14. Do đó, mã của bạn thực sự sử dụng các phần mở rộng không chuẩn.
LF
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.